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】:
原来唯一的问题不是useCallback
、useMemo
或类似的东西。
在父组件的渲染函数中我没有直接使用
return isSelected.map(...)
我从一个单独的非常简单的组件中包含了该部分,如下所示:
const Fields = () =>
return isSelected.map((isFieldSelected, i) => (
<TheChildComponent
key=i
isSelected=isFieldSelected
onClick=onClick
/>
));
;
这就是我的问题所在。当将代码从单独的组件Fields
移动到父组件的返回语句中时,重新渲染错误消失了。
仍然,感谢您的帮助。
【讨论】:
啊,不错。这个问题应该解决的另一种方法是如果你写了const Fields = React.memo(() => ... );
。 (假设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 中的图像