减少 React table 重新渲染

Reducing React table rerendering

我有一个包含两列的 table,每列都是 editable。 table 还包括添加新条目的能力。我 运行 遇到的问题是,每当编辑一行时,每一行都会重新呈现。对于较大的 tables,这会在编辑单行时导致明显的击键延迟。关于如何防止这种情况的任何想法?

我无法在“小”示例中重现性能问题,但 https://codesandbox.io/s/zen-williams-r8pii?file=/src/App.js 是基本功能。我试过在几个地方删除 React.memo 但无济于事(我是这方面的新手)

感谢您的帮助!

有些事情导致行重新呈现:

首先,行组件需要包裹在React.memo

const ThingRow = React.memo(({ thing, onUpdate }) => {
   ...
})

然后 onUpdate 也需要与 React.useCallback 一起记忆,并且为了将 useCallback 中的依赖性减少到仅 setThings(见注释),您可以使用回调版本setThings.map 更新项目

const ListOfThings = ({ things, setThings }) => {
  const onUpdate = React.useCallback(
    (thing) => {
      setThings((prevThings) =>
        prevThings.map((t) => (t.id === thing.id ? thing : t))
      );
    },
    [setThings]
  );
  return (
    <table>
      <tbody>
        {things.map((thing) => (
          <ThingRow key={thing.id} thing={thing} onUpdate={onUpdate} />
        ))}
      </tbody>
    </table>
  );
};

当遇到这种问题时,你需要寻找方法来记忆你传递的道具,尤其是回调,并减少钩子中的依赖

这是 codesandbox 分支 https://codesandbox.io/s/table-row-memoization-xs1d2?file=/src/App.js

注:根据react docs

React guarantees that setState function identity is stable and won’t change on re-renders

您可以尝试一些优化。 首先,如果您要处理大量数据,核心问题可能是您向 DOM 渲染了太多内容。 如果是这种情况,我将首先向 table 添加虚拟化或分页。 您可以使用库轻松添加虚拟化,例如使用来自 react-window:

的 FixedSizeList
import { FixedSizeList as List } from 'react-window';
 
const Row = ({ index, style }) => (
  <div style={style}>Row {index}</div>
);
 
const Example = () => (
  <List
    height={150}
    itemCount={1000}
    itemSize={35}
    width={300}
  >
    {Row}
  </List>
);

这应该已经减少了任何性能问题,但如果您只想优化重新渲染: React 的默认行为是渲染调用 useState setter 的当前组件,以及所有 child 组件,即使没有任何道具更改。 所以你应该在备忘录中包含 ListOfThings 和 ThingRow 以选择退出此行为:

const ListOfThings = memo({ things, setThings }) => {
   ...
});
const ThingRow = memo(({ thing, onUpdate }) => {
   ...
});

如果不这样做,更改不相关的标题输入将导致重新呈现所有内容。

最后,您要将内联事件处理程序 (onUpdate) 传递给 ThingRow。 这将为 ListOfThings 的每个渲染创建一个新函数 object,这将绕过我们之前进行的备忘录优化。 要对此进行优化,您可以使用 useCallback:

创建对该函数的持久引用
onUpdate={useCallback(
  thing => setThings(prev => prev.map(t => t.id === thing.id ? thing : t)),
  [setThings])}

请注意,当您依赖先前的更新值时,最好在 useState 中使用函数更新样式。

小免责声明:如果您遇到性能问题,我只会使用虚拟化/分页解决方案,而不会尝试针对重新渲染进行优化。 React 的默认渲染行为非常快,更改它可能会引入难以发现的与未更新的事物相关的错误。