Array.map 中的 React 功能组件在将函数作为道具传递时总是重新渲染

Posted

技术标签:

【中文标题】Array.map 中的 React 功能组件在将函数作为道具传递时总是重新渲染【英文标题】:React functional components in Array.map are always rerendering when getting passed a function as props 【发布时间】:2021-10-10 18:23:25 【问题描述】:

我正在尝试在集中管理所有子状态的父组件中呈现多个按钮。这意味着父存储例如点击状态,每个按钮的禁用状态在他自己的状态下使用useState 并将其作为道具传递给孩子。此外,onClick 函数也在父组件内部定义并传递给每个子组件。目前我正在这样做:

const [isSelected, setIsSelected] = useState(Array(49).fill(false));
...
const onClick = useCallback((index) => 
     const newIsSelected = [...prev];
     newIsSelected[i] = !newIsSelected[i];
     return newIsSelected;
, []);
...

(In the render function:)

return isSelected.map((isFieldSelected, key) => 
     <React.Fragment key=key>
          <TheChildComponent
               isSelected=isFieldSelected
               onClick=onClick
          />
     </React.Fragment/>
)

为了防止子组件重新渲染我正在使用...

...useCallback 让 react 看到 onClick 函数始终保持不变 ...React.Fragment 让 react 再次找到一个组件,否则孩子将没有唯一的 id 或类似的东西

子组件导出为:

export default React.memo(TheChildComponent, compareEquality)

 const compareEquality = (prev, next) => 
      console.log(prev, next);
      return prev.isSelected === next.isSelected;
 

不知何故,compareEquality 中的日志行永远不会执行,因此我知道compareEquality 永远不会执行。我也不知道为什么会这样。

我检查了所有博客、以前的 *** 问题等,但还没有找到一种方法来防止每次至少有一个组件执行 onClick 函数时重新呈现子组件,并通过这样做更新了 @987654331 @状态。

如果有人能指出我正确的方向或解释我的问题出在哪里,我会非常高兴。

提前致谢!

【问题讨论】:

你的意思是export default React.memo(TheChildComponent, compareEquality)useMemo 不正确。 哎呀,是的,它是 React.memo 而不是 useMemo。刚刚在问题中纠正了它。谢谢。 您可以尝试将[] 的第二个参数添加到useCallback 吗?如果没有 deps 列表,useCallback 无效。 (这并不能解释为什么不调用 compareEquality,但它会消除对它的需要。) 【参考方案1】:

原来唯一的问题不是useCallbackuseMemo 或类似的东西。

在父组件的渲染函数中我没有直接使用

return isSelected.map(...)

我从一个单独的非常简单的组件中包含了该部分,如下所示:

const Fields = () => 

     return isSelected.map((isFieldSelected, i) => (
       <TheChildComponent
            key=i
            isSelected=isFieldSelected
            onClick=onClick
       />
     ));

;

这就是我的问题所在。当将代码从单独的组件Fields 移动到父组件的返回语句中时,重新渲染错误消失了。

仍然,感谢您的帮助。

【讨论】:

啊,不错。这个问题应该解决的另一种方法是如果你写了const Fields = React.memo(() =&gt; ... );。 (假设Fields 是在顶层定义的,而不是在另一个组件中。)我总是将所有组件包装在React.memo 中。【参考方案2】:

这段代码实际上会在每次渲染时生成一个新的onClick 函数,因为useCallback 没有被赋予一个deps 数组:

const onClick = useCallback((index) => 
     const newIsSelected = [...prev];
     newIsSelected[i] = !newIsSelected[i];
     return newIsSelected;
);

以下内容应该只创建一个onClick 函数并在所有渲染中重复使用它:

const onClick = useCallback((index) => 
     const newIsSelected = [...prev];
     newIsSelected[i] = !newIsSelected[i];
     return newIsSelected;
, []);

结合原版React.memo,这应该可以防止子级重新渲染,除非isSelected 发生变化。 (你对React.memo 的第二个参数也应该解决了这个问题——我不知道为什么这不起作用。)

附带说明,您可以简化此代码:

     <React.Fragment key=key>
          <TheChildComponent
               isSelected=isFieldSelected
               onClick=onClick
          />
     </React.Fragment/>

到以下:

          <TheChildComponent key=key
               isSelected=isFieldSelected
               onClick=onClick
          />

(假设您确实只需要map 正文中的一个组件。

【讨论】:

感谢您的回答。我添加了deps 数组,但问题仍然存在。 (也更新了问题) 您发布的代码中的其他所有内容看起来都不错,因此您可能需要发布指向完整代码库的链接,例如在codesandbox.io 好的,非常感谢。我自己发现了错误并发布了答案。

以上是关于Array.map 中的 React 功能组件在将函数作为道具传递时总是重新渲染的主要内容,如果未能解决你的问题,请参考以下文章

React:使用 array.find 和 array.map 方法设置状态有啥区别? [复制]

使用 array.map() 创建组件时如何动态分配 Prop 值

即使 .map 循环通过数组的所有索引,也无法在我的 React 应用程序中的 array.map 中显示我的 JSX 中的图像

如何将firebase与react功能组件同步?

获取 React 渲染中 map 返回的数组大小

如何在将 json 发布到 React 组件状态之前对其进行处理?