React:如何防止在`map`中重新渲染子组件?

Posted

技术标签:

【中文标题】React:如何防止在`map`中重新渲染子组件?【英文标题】:React: How to prevent re-rendering child components in `map`? 【发布时间】:2020-06-17 02:48:14 【问题描述】:

我试图将问题归结为一个尽可能简单的例子:

我们有一个子组件列表,每个子组件都称为NumChoice,每个子组件代表一个数字。 NumChoice 包裹在 React.memo 中。在父组件中,我们有一个布尔数组choices,每个都对应一个子组件NumChoice。起初,choices 的所有元素都是false。为了渲染子组件,我们遍历choices,并为每个选择生成对应的子组件NumChoice。我们在父组件中定义一个函数chooseDivisibles,使用从每个子组件NumChoice 调用的useCallbackchooseDivisibles 获取调用它的NumChoice 的索引,并将choices 的对应元素更改为true。每个NumChoice如果choices中对应的元素为true,则其背景颜色为“红色”,否则,其背景颜色为“白色”。

完整的代码可在以下位置获得: https://codesandbox.io/s/react-rerender-l4e3c?fontsize=14&hidenavigation=1&theme=dark

NumChoice 包装在React.memochooseDivisiblesuseCallback 中,我们希望只重新渲染NumChoice 的组件,其对应的choices 元素发生变化,但React 会重新渲染它们。 chooseDivisibles 被包裹在 useCallback 中,除了 setChoices 之外没有列出任何依赖项。此外,NumChoice 被包裹在 React.memo 中,它应该只在指定的 props 发生变化时重新渲染,但它们不会,并且更改 choices 不应该对重新渲染 NumChoice 产生任何影响。如果我们排除在上一个和下一个道具中检查chooseDivisibles 的相等性,它会按预期工作,但我认为上一个和下一个chooseDivisibles 的比较不应该影响重新渲染NumChoice,因为它包含在useCallbackand不依赖于choices。我们如何防止重新渲染 props 未更改的 NumChoice 组件?

【问题讨论】:

在您添加 prevProps.chooseDivisibles === nextProps.chooseDivisiles 之前,一切似乎都运行良好,分析器没有显示其 props 保持相同重新渲染的组件。 我认为您的建议并不能解决问题,因为如果我们将等式更改为 (prevProps, nextProps) => prevProps === nextProps,它会重新渲染所有组件。考虑到chooseDivisibles 被包裹在useCallback 中,它列出了除setChoices 之外的任何依赖项,因此更改状态不应更改chooseDivisibles 并且不应重新渲染NumChoice 如果您尝试相同的事情而不使用map through一个数组,这个问题不会发生。请看一下我刚刚保存的新代码。 据我所知,React 已经(浅)将以前的 props 与新的 props 对象进行比较,所以这样做 prevProps === newProps 是多余的,实际上我认为它是错误的,因为结果将永远是false 不管你是否这样做,因为对象是通过引用传递的。 @eMontielG 你的回答是对的。谢谢你。请发布它,我会接受它作为正确答案。为了解决您的问题,我将chooseDivisibles=event => chooseDivisibles(idx) 更改为chooseDivisibles=chooseDivisibles,并将NumChoice 中的函数调用替换为onClick=event => props.chooseDivisibles(props.num)。然后,我比较了prevProps.num === nextProps.num && prevProps.choice === nextProps.choice && prevProps.chooseDivisibles === nextProps.chooseDivisibles,它按预期工作。 顺便说一句,我想我发现了你的问题,你也应该记住循环选择的组件。 【参考方案1】:

啊,我看到在NumChoice.js 中我们还断言prevProps.chooseDivisibles === nextProps.chooseDivisibles,它始终是false,因为chooseDivisibles=event => chooseDivisibles(idx) 每次都会生成一个新函数

如果你删除prevProps.chooseDivisibles === nextProps.chooseDivisibles,它只会重新渲染受影响的!

【讨论】:

我认为您的建议并不能解决问题,因为如果我们将等式更改为(prevProps, nextProps) => prevProps === nextProps,它会重新渲染所有组件。考虑到chooseDivisibles 被包裹在useCallback 中,它列出了除setChoices 之外的任何依赖项,因此更改状态不应更改chooseDivisibles 并且不应重新渲染NumChoice 如果您尝试相同的事情而不使用map through一个数组,这个问题不会发生。 为了解决您的问题,我将chooseDivisibles=event => chooseDivisibles(idx) 更改为chooseDivisibles=chooseDivisibles 并将NumChoice 中的函数调用替换为onClick=event => props.chooseDivisibles(props.num),但它仍然会重新渲染所有NumChoice 组件。 嘿@1man 对不起,我的意思是我们可以注释掉断言函数。像这样:(prevProps, nextProps) => prevProps.num === nextProps.num && prevProps.choice === nextProps.choice // prevProps.chooseDivisibles === nextProps.chooseDivisibles <-- comment this out 分叉示例:codesandbox.io/s/… 我们找到了解决方案,但我们不应该评论我们的prevProps.chooseDivisibles === nextProps.chooseDivisibles,因为chooseDivisibles 被包裹在useCallback 中,这样可以防止在choices 更改时更改函数对象。所以,我们不需要注释掉这个比较。但是,如果我们将其注释掉并且由于某些其他原因 chooseDivisibles 更改,我们确实希望重新渲染 NumChoice 啊,很高兴听到这个消息!感谢更新?我刚刚更新了示例,将<App />中的一些关注点分开,所以我们可以简化<NumChoice />的界面,使其备忘录功能更直接codesandbox.io/s/…请告诉我你的想法!跨度>

以上是关于React:如何防止在`map`中重新渲染子组件?的主要内容,如果未能解决你的问题,请参考以下文章

如何防止 react 重新渲染整个组件?

如何防止我的功能组件使用 React 备忘录或 React 钩子重新渲染?

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

使用 setInterval 时如何防止 React 重新渲染整个组件

React(性能):如何防止每次状态更改时出现不必要的渲染? [复制]

React 功能组件在重新挂载后停止渲染状态更改