繁体   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