简体   繁体   English

如何避免 React.js 中的 useEffect 和 useCallback 依赖循环

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

I am working on converting a React component from a class component to a functional component with hooks.我正在将 React 组件从 class 组件转换为带有钩子的功能组件。

I want to get a list of group members from my database.我想从我的数据库中获取组成员列表。 I hold the array in component state.我将数组保存在组件 state 中。

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

Once the members are downloaded, I want to get each member's profile picture asynchronously.下载成员后,我想异步获取每个成员的个人资料图片。

1) The component is mounted, and the following useEffect() is called. 1) 组件被挂载,调用下面的useEffect() Note the dependency to getMembers .注意对getMembers的依赖。

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

2) The useEffect callback calls the function getMembers() . 2) useEffect回调调用 function getMembers() Note the dependency to getMembersProfilePictures .请注意对getMembersProfilePictures的依赖关系。

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

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

3) Once the members are retrieved from the database, the members state is updated and getMembersProfilePictures() is called. 3) 一旦从数据库中检索到成员, members state 就会更新并getMembersProfilePictures() Note the dependency to members .注意对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]);

Because the useEffect() depends on getMembers() , getMembers() depends on getMembersProfilePictures() , and getMembersProfilePictures() depends on members , as soon as the members state is updated, the chain of useCallback() s is re-created, and the useEffect() is called.因为useEffect()依赖于getMembers()getMembers()依赖于getMembersProfilePictures() ,而getMembersProfilePictures()依赖于members ,所以一旦members state 更新, useCallback()链就会重新创建,并且调用useEffect() This becomes an infinite loop of data fetching.这成为数据获取的无限循环。

My current thought is to pass the data retrieved from fetchMembersFromDatabase() directly to getMembersProfilePictures() as an argument.我目前的想法是将fetchMembersFromDatabase()检索到的数据直接传递给getMembersProfilePictures()作为参数。 This removes the dependency of members from getMembersProfilePictures() , and therefore removes the infinite loop.这从getMembersProfilePictures()中删除了members的依赖关系,因此删除了无限循环。

Ignoring listening to changes in the members list in the database and ignoring caching of members and their respective profile picture, it appears that there are no drawbacks to this solution.忽略监听数据库中成员列表中的更改并忽略成员及其各自的个人资料图片的缓存,似乎该解决方案没有缺点。 I am wondering what other's thoughts are to this solution.我想知道其他人对此解决方案的想法是什么。 Thanks!谢谢!

  • You are calling fetchMembersFromDatabase() in the useCallback but not using it as a dependency.您在useCallback中调用fetchMembersFromDatabase()但不将其用作依赖项。
  • You are setting members inside getMembersProfilePictures() which is using members as dependency.您正在getMembersProfilePictures()中设置members ,它使用成员作为依赖项。 This is causing infinite loop in my opinion.在我看来,这导致了无限循环。

Proposed solution建议的解决方案

  1. useEffect without any dependency没有任何依赖useEffect
  2. useEffect for member and call the get member images function.对成员使用Effect并调用获取成员图像useEffect maintain another object for member thumbnails.为会员缩略图维护另一个 object。

EDIT: It's become clear you're trying to do too much in one component.编辑:很明显,您试图在一个组件中做太多事情。

What you really want to do is你真正想做的是

  1. Get your list of members and render Member components and then获取您的成员列表并呈现Member组件,然后
  2. Have your Member components download their own pictures.让您的Member组件下载自己的图片。

The below is semi-pseudo code, take the async/await stuff with a grain of salt and do it the proper way that works for you:下面是半伪代码,对 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 (...)

}

Original answer:原答案:

Ever consider separating your state?是否考虑过分离您的 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])}/>

The major issue with your original code is that you were calling setMembers twice, which is a big indicator that what you think is one piece of state, is probably better served as being two pieces of state.您的原始代码的主要问题是您调用了两次setMembers ,这是一个重要指标,表明您认为是 state 的一部分,可能更好地作为两块 state。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM