我制作了一个多选项列表,并一直在努力提高它的重新渲染性能.下面的代码显示了我所做的(在我的实际项目中,列表要长得多).

<div id="app" />
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'))
"react": "17.0.2",
"react-dom": "17.0.2",

推荐答案

首先,我们已经知道我们的目标是防止动物受到不必要的伤害.我们再加上React.memo:

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

当然,它现在什么都没做.我们可以清楚地看到,nameselected是静态值.它们只是字符串和布尔值,所以它们应该能够很好地处理React.memo.问题是onClick.

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

const makeOnClick  = (item: string) => () => {
  // ...
}
makeOnClick(i)

在每个渲染中,生成的函数总是不同的.当前代码类似于:

<Animal onClick={() => onClick(i)} />

这对React永远不起作用.备忘录,因为它总是在创建一个新函数.所以我们的目标是传递一个静态函数给它.让我们将其修改为:

<Animal onClick={onClick} />

但我们仍然需要知道i.所以在动物成分中,我们可以做到:

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

在这种情况下,如果onClick是静态值,则动物组件不会进行不必要的渲染.所以我们的最终目标是为动物创建一个静态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)
}

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

幸运的是,setState实际上支持回调函数.对于exmaple:

setItems(items => [...items, 'foo'])

也就是说,我们可以访问当前的items,而不必依赖于items.因此,我们只需更改setItems即可使用更新功能:

const onClick = React.useCallback((item: string) => {
  setItems((items) => {
    const ix = items.indexOf(item);
    const newItems =
      ix > -1
        ? [...items.slice(0, ix), ...items.slice(ix + 1)]
        : [...items, item];
    return newItems;
  });
}, []);

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

您可以使用React Developer工具判断渲染,或者只添加一些conosle.登录到动物组件.

Reactjs相关问答推荐

我可以使用全局变量而不是React.上下文来在这里传递常数props 吗?

NextJS(AppRouter)、Apollo(客户端),在根布局中使用ApolloProvider时出错

如何将Redux工具包添加到expo (SDK 50)项目?

React,React Router,Joy UI:如何在导航离开和返回后禁用snackbar重新出现?

错误./src/TopScroll.js 31:21-31导出';(作为';随路由导入)在';Reaction-Router-Dom';中找不到

如何使用皮金贴图在Reactjs中制作 map 上的点之间的线的动画?

如何在整个应用程序中显示通用弹出窗口中的点击事件

当项目开始时,它不会从三元运算符(REACT)加载一些tailwind 类名称

shadcn输入+窗体+Zod;一个部件正在改变要被控制的不受控制的输入;

在 React 中,如何在指向同一组件的路由中拥有未定义数量的多个动态路径段?

设置自定义形状的纹理

REACT: UseState 没有更新变量(ant design 模态形式)

当我在 React Router Dom 中使用两个参数并直接在页面上导航时,数据获取没有发生

将 useState 设置为组件内部的值

动态添加或删除文本输入字段并将值设置为 React JS 中主要状态的数组

如何在对话素材ui中设置边框半径?

setState 改变对象引用

使用 useRef(uuid()) 的目的是什么?

无法重用我的组件,它只显示 1 次

MUI 卡组件上的文本未正确对齐