【问题标题】:How to Avoid a useEffect and useCallback Dependency Loop in React.js如何避免 React.js 中的 useEffect 和 useCallback 依赖循环
【发布时间】:2020-08-31 17:26:41
【问题描述】:

我正在努力将 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,所以一旦members 状态更新,useCallback() 的链s 被重新创建,useEffect() 被调用。这变成了数据获取的无限循环。

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

忽略监听数据库中成员列表的变化,忽略缓存成员及其各自的头像,看来这个解决方案没有缺点。我想知道其他人对此解决方案的想法是什么。谢谢!

【问题讨论】:

  • getMembersgetMembersProfilePictures 是同一个回调的一部分,您不必要地将它们分开并创建了一个依赖循环。
  • @Adam 分开是什么意思?这两个功能是否应该合并为一个功能?如果是这样的话,我相信我仍然会有一个依赖循环。
  • 不会,因为getMembersProfilePictures 将不再依赖members,因为members 直接来自fetchMembersFromDatabase 调用。
  • 我有一个更好的解决方案给你——创建一个“会员”组件,让每个会员组件负责下载自己的图片,你的“顶级”组件应该只关心获取列表成员。
  • 99% 的开发人员试图从他们的组件中创建迷你应用程序,而不是仅仅创建极小的、几乎愚蠢的小组件。我最喜欢的组件类型是如此之小以至于可笑,例如 OptionalThemeComponent = ({theme,children}) =&gt; theme ? &lt;ThemeProvider theme={theme}&gt;{children}&lt;/ThemeProvider&gt; : children 这就是基于组件的开发应有的样子 - 组件如此之小以至于您可以在 1 分钟内了解它们所做的一切。

标签: javascript reactjs react-hooks


【解决方案1】:
  • 您在useCallback 中调用fetchMembersFromDatabase() 但是 不将其用作依赖项。
  • 你在getMembersProfilePictures()里面设置members 正在使用成员作为依赖项。这导致我的无限循环 意见。

建议的解决方案

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

【讨论】:

  • 我同意在getMembersProfilePictures() 内设置members 是导致无限循环的原因。 fetchMembersFromDatabase() 函数声明在此组件函数之外,因此不是依赖项。聪明的解决方案 - 谢谢!
【解决方案2】:

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

你真正想做的是

  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,这是一个重要指标,表明您认为是一个状态,可能更好地作为两个状态。

【讨论】:

  • 我对这种方法唯一关心的是我必须创建一个映射/指针来将图片映射到成员。并非所有成员都有图片,因此我要么必须在成员状态和 memberPic 状态中保存成员的 UID,要么必须在 memberPics 数组中保留空索引以便按数组索引进行映射。这不是 30 个成员的内存问题,但在处理 500 + 个成员时会浪费内存或重复数据。有没有更有效的方法将图片映射到成员?任何代码都存在时间/空间/可读性权衡...
  • 我喜欢编辑!它分离了职责,当只有一张图片进来时,去除了所有成员的重新渲染,并且去除了无限循环。非常感谢!
猜你喜欢
  • 2021-09-15
  • 2013-02-06
  • 1970-01-01
  • 2012-02-15
  • 2013-04-02
  • 2012-08-10
  • 1970-01-01
  • 2021-01-14
  • 1970-01-01
相关资源
最近更新 更多