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

时间:2020-05-15 00:53:06

标签: javascript reactjs react-hooks

我正在使用钩子将React组件从类组件转换为功能组件。

我想从数据库中获取组成员的列表。我将数组保持在组件状态。

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

成员下载后,我想异步获取每个成员的个人资料图片。

1)组件已安装,并调用以下useEffect()。请注意对getMembers的依赖性。

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

2)useEffect回调调用函数getMembers()。请注意对getMembersProfilePictures的依赖性。

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

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

3)一旦从数据库中检索到成员,members状态就会更新并调用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,因此{ {1}}状态被更新,members的链被重新创建,并且useCallback()被调用。这成为数据获取的无限循环。

我目前的想法是将从useEffect()检索到的数据作为参数直接传递给fetchMembersFromDatabase()。这从getMembersProfilePictures()中删除了members的依存关系,因此删除了无限循环。

忽略侦听数据库中成员列表中的更改,并忽略成员及其各自的个人资料图片的缓存,看来此解决方案没有缺点。我想知道其他人对此解决方案有何想法。谢谢!

2 个答案:

答案 0 :(得分:2)

  • 您正在fetchMembersFromDatabase()中呼叫useCallback,但 不将其用作依赖项。
  • 您正在members内设置getMembersProfilePictures(),其中 使用成员作为依赖项。这导致我的无限循环 意见。

建议的解决方案

  1. useEffect没有任何依赖关系
  2. useEffect用于会员和致电 获取成员图像功能。为成员维护另一个对象 缩略图。

答案 1 :(得分:1)

编辑:很明显,您试图在一个组件中做太多事情。

您真正想做的是

  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 (...)

}

原始答案:

是否考虑过分开您的州?

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,这很重要地表明您认为是一种状态,最好将其视为两种状态。