【问题标题】:Render only components with changes仅渲染有更改的组件
【发布时间】:2022-01-05 19:07:27
【问题描述】:

我有一个包含数千个字符串的数组并被传递给一个组件:

主要组件:

const array = ['name1', 'name2', 'name3'];
const [names, setNames] = useState(array);

const onClick = (index) => {
  setNames(names.map((name, i) => {
    if (i === index) {
      return 'name changed';
    }
  }; 
};

return (
  <ul>
    {array.map((name, index) => (
      <li key={index}>
        <ShowName name={name} key={index} onClick={() => onClick(index)} />
      </li>
    )}
  </ul>
);

ShowName 组件:

let a = 0;

export default function ShowName({ name, onClick }) {
  a += 1;
  console.log(a);

  return (
    <button type="button" onClick={onClick}>{name}</button>
  );
}

还有一个随机更改名称的按钮。但是每当按下按钮时,所有ShowName 组件都会重新渲染。我一直在尝试使用 useCallback 和 useMemo,但是组件仍在重新渲染 x 次(x 是数组的长度)。

const ShowNameHoc = ({ name }) => {
  return <ShowName name={name} />
};

return (
  <div>
    {array.map((name, index) => <ShowNameHoc name={name} key={index} />)}
  </div>
);

如果我只想重新渲染带有更改的组件,该怎么办?

【问题讨论】:

  • 您使用数组映射来渲染每个项目,为什么您认为它不会渲染每个项目?
  • @andymccullough 你有什么建议可以让我更有效地做到这一点吗?
  • 您是如何得出每个组件都重新渲染的结论的?
  • 我添加了一个计数器,每次单击按钮时,计数都会增加数组长度的确切数量。

标签: javascript reactjs render usecallback react-usememo


【解决方案1】:

您对这里的概念有误解。默认情况下,React 会在所有子节点上调用 render,无论 props 是否更改。
在那之后,React 会将新的 Virtual DOM 与当前的 Virtual DOM 进行比较,然后只更新真实 DOM 中发生变化的那些部分。 这就是为什么渲染方法中的代码应该可以快速执行的原因。

可以使用useMemoPureComponentsshouldComponentUpdate 等功能更改此行为。

参考:
https://reactjs.org/docs/rendering-elements.html(底部):

即使我们在每个刻度上创建一个描述整个 UI 树的元素,但只有内容发生变化的文本节点才会被 React DOM 更新。

https://reactjs.org/docs/optimizing-performance.html#avoid-reconciliation

即使 React 只更新更改的 DOM 节点,重新渲染仍然需要一些时间。在许多情况下,这不是问题,但如果减速很明显,您可以通过覆盖生命周期函数 shouldComponentUpdate 来加快所有速度,该函数在重新渲染过程开始之前触发。
...
在大多数情况下,您可以从 React.PureComponent 继承,而不是手动编写 shouldComponentUpdate()。相当于实现 shouldComponentUpdate() 对当前和以前的 props 和 state 进行浅比较。

另外,请阅读本文了解更多背景信息:https://dev.to/teo_garcia/understanding-rendering-in-react-i5i


更多细节:
在 React 中广义上的渲染意味着(简化):

  1. 在可行的情况下使用新的 props 更新现有组件实例(这是列表键很重要的地方)或创建一个新实例。
  2. 如果shouldComponentUpdate返回true,则调用render/代表组件的函数
  3. 将更改同步到真实 DOM

这为您提供了以下优化可能性:

  1. 确保您正在重用实例而不是创建新实例,例如通过在渲染列表时使用正确的键。为什么?新实例总是会导致旧 DOM 节点从真实 DOM 中移除并添加一个新节点。即使不变。重用实例只会在必要时更新真实的 DOM。请注意:这对您的组件是否正在调用 render 没有影响。
  2. 确保您的渲染方法不会执行繁重的工作,如果有,请记住这些结果
  3. 使用 PureComponents 或 shouldComponentUpdate 在 props 未更改的情况下完全阻止对 render 的调用

回答您的具体问题:
要真正防止您的 ShowName 组件被渲染到虚拟 DOM 中 - 如果它们的道具发生变化,您需要执行以下更改:

  1. 在 ShowName 组件上使用 React.memo
function ShowName({ name, onClick }) {
  return (
    <button type="button" onClick={onClick}>{name}</button>
  );
}

export default memo(ShowName);
  1. 通过在每次渲染父对象时不向onClick 传递新的回调来确保道具实际上没有改变。 onClick={() =&gt; onClick(index)} 每次渲染父级时都会创建一个新的匿名函数。
    防止这种情况有点棘手,因为您想将索引传递给这个 onClick 函数。一个简单的解决方案是创建另一个组件,通过参数和索引传递 onClick,然后在内部使用useCallback 构造一个稳定的回调。不过,这只有在渲染 ShowName 是一项昂贵的操作时才有意义。

【讨论】:

  • 感谢您的精彩阅读,我认为您是对的,我希望使用 useMemo 做一些事情来防止不必要地重新渲染孩子,但我认为我应该重新考虑我的整个数据结构。
  • @HennyLee:我添加了更多细节,请检查
【解决方案2】:

这是因为您没有在 &lt;ShowName/&gt; 组件上使用 key 属性。 https://reactjs.org/docs/lists-and-keys.html
它可能看起来像这样

 return (
<div>
    {array.map(name => <ShowName key={name} name={name} />)}
</div>
);

【讨论】:

  • 抱歉,我使用的是密钥,我只是忘记将它添加到示例中。
  • 这让我想知道还有什么不包括在内。你能添加整个组件,父母和孩子吗?作为旁注,不要使用索引作为键
  • 我尽可能地简化并改变了我的OP。
  • -1:这是不正确的。使用 key 与渲染组件无关。它只是用来将新的 props 与正确的以前的 props 对应起来,以便能够正确更新 shouldComponentUpdate 并防止对真实 DOM 进行不必要的更新。
  • 嗯,如果这不正确,您可能需要向文档发出 pr。 reactjs.org/docs/lists-and-keys.htmlreactjs.org/docs/reconciliation.html#recursing-on-children@DanielHilgarth
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-05-17
  • 2016-11-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-16
  • 1970-01-01
相关资源
最近更新 更多