簡體   English   中英

如何避免 React.js 中的 useEffect 和 useCallback 依賴循環

[英]How to Avoid a useEffect and useCallback Dependency Loop in React.js

我正在將 React 組件從 class 組件轉換為帶有鈎子的功能組件。

我想從我的數據庫中獲取組成員列表。 我將數組保存在組件 state 中。

const [members, setMembers] = useState([]);

下載成員后,我想異步獲取每個成員的個人資料圖片。

1) 組件被掛載,調用下面的useEffect() 注意對getMembers的依賴。

useEffect(() => {
    getMembers();
}, [getMembers]);

2) useEffect回調調用 function getMembers() 請注意對getMembersProfilePictures的依賴關系。

const getMembers = useCallback(() => {
    fetchMembersFromDatabase()
        .then((data) => {
            setMembers(data);

            getMembersProfilePictures();
        })
}, [getMembersProfilePictures]);

3) 一旦從數據庫中檢索到成員, members state 就會更新並getMembersProfilePictures() 注意對members的依賴。

const getMembersProfilePictures = useCallback(() => {
    for (let i = 0; i < members.length; i++) {
        const member = { ...members[i] };

        if (member.has_picture) {
            firebase
                .storage()
                .ref()
                .child("<childUrl>")
                .getDownloadURL()
                .then((url) => {
                    member.picture_url = url;
                    const membersCopy = [...members];
                    membersCopy[i] = member;
                    setMembers(membersCopy);
                });
        }
    }
}, [members]);

因為useEffect()依賴於getMembers()getMembers()依賴於getMembersProfilePictures() ,而getMembersProfilePictures()依賴於members ,所以一旦members state 更新, useCallback()鏈就會重新創建,並且調用useEffect() 這成為數據獲取的無限循環。

我目前的想法是將fetchMembersFromDatabase()檢索到的數據直接傳遞給getMembersProfilePictures()作為參數。 這從getMembersProfilePictures()中刪除了members的依賴關系,因此刪除了無限循環。

忽略監聽數據庫中成員列表中的更改並忽略成員及其各自的個人資料圖片的緩存,似乎該解決方案沒有缺點。 我想知道其他人對此解決方案的想法是什么。 謝謝!

  • 您在useCallback中調用fetchMembersFromDatabase()但不將其用作依賴項。
  • 您正在getMembersProfilePictures()中設置members ,它使用成員作為依賴項。 在我看來,這導致了無限循環。

建議的解決方案

  1. 沒有任何依賴useEffect
  2. 對成員使用Effect並調用獲取成員圖像useEffect 為會員縮略圖維護另一個 object。

編輯:很明顯,您試圖在一個組件中做太多事情。

你真正想做的是

  1. 獲取您的成員列表並呈現Member組件,然后
  2. 讓您的Member組件下載自己的圖片。

下面是半偽代碼,對 async/await 的內容持保留態度,並以適合您的正確方式進行操作:

const MemberList = ({maybeWithAProp}) => {

   const [members,setMembers] = useState([]);
   useEffect(async () => {
       setMembers((await someCallToGetMembers(maybeWithAProp));
   },[maybeWithAProp]);

   return members.map(m => <Member {...m}/>

}

const Member = ({memberId}) => {

  const [pic,setPic] = useState();

  useEffect(async () => {
     setPic(null); // you're about to download a new one, probably get rid of the old one first
     setPic((await fetchPicForMember(memberId));
  },[memberId]);

  return (...)

}

原答案:

是否考慮過分離您的 state?

const [members, setMembers] = useState([]);
const [membersPics,setMembersPics] = useState([]);

// run on mount only
useEffect(async () => {
  const data = await fetchMembersFromDatabase();
  setMembers(data);
,[]);

useEffect(async () => {
  const pics = await Promise.all(members.map(m => fetchProfilePic(m)));
  setMembersPics(pics);
},[members]);


return <SomeComponent members={useMemo(members.map((m,i) => ({...m,url:membersPics[i]})),[members,memberPics])}/>

您的原始代碼的主要問題是您調用了兩次setMembers ,這是一個重要指標,表明您認為是 state 的一部分,可能更好地作為兩塊 state。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM