优化带有闭包的可选列表项的性能

Wat*_*e.N 8 reactjs

我制作了一个多选列表,并一直在努力提高其重新渲染性能。下面的代码显示了我所做的(在我的实际项目中,列表要长得多)。目前,当我单击列表项时,它会重新呈现每一行。我只想渲染单击的行。看起来 React.memo、React.useCallback 是可能的,但我还没有成功地在这个用例中正确使用它们。欢迎任何反馈!提前致谢。

<div id="app" />
Run Code Online (Sandbox Code Playgroud)
const animals = ['Cat', 'Dog', 'Bird', 'Fish']

interface AnimalProps {
  selected: boolean;
  name: string;
  onClick: (name: string) => void;
}
const Animal = ({selected, name, onClick}: AnimalProps) => (
  <div onClick={onClick}>{`[${selected ? 'X' : ' '}]`} {name}</div>
)

const useFilter = (initialValue: string[]) => {
  const [items, setItems] = React.useState<string[]>(initialValue)
  const makeOnClick  = (item: string) => () => {
    const ix = items.indexOf(item)
    const newItems = (ix > -1) ?
      [...items.slice(0, ix), ...items.slice(ix + 1)]
      :
      [...items, item]
    setItems(newItems)
  }
  const isSelected = (item: string) => items.indexOf(item) > -1
  return { items, makeOnClick, isSelected };
 }

const App = () => {
  const {items, makeOnClick, isSelected} = useFilter([])
  return (
    <div>
      <ul>
        {animals.map(i => (
          <Animal name={i} selected={isSelected(i)} onClick={makeOnClick(i)} key={i} />
        ))}
      </ul>
      <div>Selected: {items.join(', ')}</div>
    </div>
  )
}

ReactDOM.render(<App />, document.querySelector('#app'))
Run Code Online (Sandbox Code Playgroud)
"react": "17.0.2",
"react-dom": "17.0.2",
Run Code Online (Sandbox Code Playgroud)

Cod*_*Cat 5

首先,我们已经知道我们的目标是防止 Animal 进行不必要的渲染。让我们添加React.memo

const Animal = React.memo(({selected, name, onClick}: AnimalProps) => (
  <div onClick={onClick}>{`[${selected ? 'X' : ' '}]`} {name}</div>
))
Run Code Online (Sandbox Code Playgroud)

当然现在它什么也没做。我们可以清楚地看到nameselected是静态值。它们只是字符串和布尔值,因此它们应该能够React.memo很好地配合使用。问题是onClick.

在此代码中,我们可以看到我们正在循环中创建新函数:

const makeOnClick  = (item: string) => () => {
  // ...
}
makeOnClick(i)
Run Code Online (Sandbox Code Playgroud)

在每一次渲染中,生成的函数始终是不同的。当前的代码类似于:

<Animal onClick={() => onClick(i)} />
Run Code Online (Sandbox Code Playgroud)

这永远不会与 React.memo 一起使用,因为它总是创建一个新函数。所以我们的目标是向它传递一个静态函数。我们只需将其修改为:

<Animal onClick={() => onClick(i)} />
Run Code Online (Sandbox Code Playgroud)

但我们仍然需要了解i。所以在 Animal 组件中,我们可以这样做:

<Animal onClick={onClick} />
Run Code Online (Sandbox Code Playgroud)

在这种情况下,如果onClick是静态值,那么 Animal 组件将不会进行不必要的渲染。所以我们的最终目标是为 Animal 创建一个静态的 onClick 函数。

而且makeOnClick不再是makeOnClick现在。我们不需要一个返回函数的函数,所以我们只需命名它onClick并删除额外的内部函数:

const onClick  = (item: string) => {
  const ix = items.indexOf(item)
  const newItems = (ix > -1) ?
    [...items.slice(0, ix), ...items.slice(ix + 1)]
    :
    [...items, item]
  setItems(newItems)
}
Run Code Online (Sandbox Code Playgroud)

现在是最后一步。我们只需要记住这个函数,但是我们可以看到它需要最新的items. 换句话说, theitems是它的依赖项。所以即使我们useCallback现在添加,我们仍然需要将其添加items到依赖项中。我们知道,items选择某个项目时进行的更改useCallback不会有任何帮助。我们必须以某种方式消除items依赖关系。

幸运的是,setState实际上支持回调函数。例如:

const Animal = React.memo(({ selected, name, onClick }: AnimalProps) => (
  <div onClick={() => onClick(name)}>
    {`[${selected ? 'X' : ' '}]`} {name}
  </div>
));
Run Code Online (Sandbox Code Playgroud)

这意味着,我们可以访问 current items,而无需items依赖。所以我们只需要更改setItems即可使用更新函数:

const onClick  = (item: string) => {
  const ix = items.indexOf(item)
  const newItems = (ix > -1) ?
    [...items.slice(0, ix), ...items.slice(ix + 1)]
    :
    [...items, item]
  setItems(newItems)
}
Run Code Online (Sandbox Code Playgroud)

现在应该可以工作了。查看完整的工作代码:https://stackblitz.com/edit/react-ts-foxpnu ?file=index.tsx

您可以使用 React Developer Tools 检查渲染,或者只是将一些 conosle.log 添加到 Animal 组件。