避免在 React 中重新渲染整个列表

时间:2021-01-07 11:20:59

标签: javascript reactjs

我有一个简单的项目列表,可以自行更新。更新一个项目会触发所有项目的重新渲染。我为项目提供了唯一键,我希望 React 会跳过未更改项目的更新。即使 App 重新创建 itemshandleItemUpdate 函数。

怎么了?

<块引用>

Codepen 示例:https://codepen.io/enepom/pen/VwKdxZN
点击一项会在控制台中打印 3 个项目渲染,而不是一个。

项目组件:

const Item = React.memo(({ id, count, onUpdate }) => {
  console.log('> ITEM RENDER', id);

  const handleClick = () => {
    onUpdate(id, count + 1);
  };

  return (
    <li onClick={handleClick}>{id}: {count}</li>
  );
});

应用组件:

const App = () => {
  const [items, setItems] = React.useState([]);

  React.useEffect(() => {
    setItems([
      { id: 'id1', count: 7 },
      { id: 'id2', count: 8 },
      { id: 'id3', count: 9 },
    ]);
  }, []);

  const handleItemUpdate = React.useCallback((itemId, count) => {
    const itemIndex = items.findIndex(item => item.id === itemId);
    if (itemIndex > -1) {
      const itemsCopy = items.slice();
      itemsCopy[itemIndex].count = count;

      setItems(itemsCopy);
    }
  }, [items, setItems]);

  return (
    <ul>
      {items.map(item => (
        <Item key={item.id} id={item.id} count={item.count} onUpdate={handleItemUpdate} />
      ))}
    </ul>
  );
};

4 个答案:

答案 0 :(得分:1)

每次更新项目时,都会重新计算 handleItemUpdate,因为 items 已更改,并且在 useCallback 依赖项数组中:

 [items, setItems]

因此,当一个属性 (onUpdate) 发生更改时,每个 Item 都会再次呈现。

答案 1 :(得分:1)

问题依赖于 items 中的 handleItemUpdate

此代码按预期工作:

const handleItemUpdate = React.useCallback((itemId, count) => {
  setItems(prevItems => prevItems.map(item =>
    item.id === itemId
      ? { ...item, count }
      : item
  ));
}, [setItems]);

代码笔:https://codepen.io/enepom/pen/qBaKYye

答案 2 :(得分:0)

这是您可以做到的一种方法。 React.memo 进行了浅层比较,因为您传递的对象可能通过引用而不同,即使值相同,您也需要使用自定义逻辑实现 arePropsEqual。

const Item = ({ id, count, onUpdate }) => {
  console.log('> ITEM RENDER', id);

  const handleClick = () => {
    onUpdate(id, count + 1);
  };

  return (
    <li onClick={handleClick}>{id}: {count}</li>
  );
};
const arePropsEqual =(next,prev)=>{
  return next.id ===prev.id &&  next.count ===prev.count
}
const MemoItem = React.memo(Item,arePropsEqual);


const App = () => {
  const [items, setItems] = React.useState([]);

  React.useEffect(() => {
    setItems([
      { id: 'id1', count: 7 },
      { id: 'id2', count: 8 },
      { id: 'id3', count: 9 },
    ]);
  }, []);

  const handleItemUpdate = React.useCallback((itemId, count) => {
    const itemIndex = items.findIndex(item => item.id === itemId);
    if (itemIndex > -1) {
      const itemsCopy = items.slice();
      itemsCopy[itemIndex].count = count;

      setItems(itemsCopy);
    }
  }, [items, setItems]);

  return (
    <ul>
      {items.map(item => (
        <MemoItem key={item.id} id={item.id} count={item.count} onUpdate={handleItemUpdate} />
      ))}
    </ul>
  );
};

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

答案 3 :(得分:0)

这是一种避免不必要渲染的方法:

向 React.memo 添加相等函数:

const Item = React.memo(({ id, count, onUpdate,items }) => {
  console.log('> ITEM RENDER', id);

  const handleClick = () => {
    onUpdate(id, count + 1,items);
  };

  return (
    <li onClick={handleClick}>{id}: {count}</li>
  );
}, (prev, next) => prev.id===next.id && prev.count===next.count);


const App = () => {
  const [items, setItems] = React.useState([]);

  React.useEffect(() => {
    setItems([
      { id: 'id1', count: 7 },
      { id: 'id2', count: 8 },
      { id: 'id3', count: 9 },
    ]);
  }, []);

  const handleItemUpdate = React.useCallback((itemId, count,items) => {
    const itemIndex = items.findIndex(item => item.id === itemId);
    if (itemIndex > -1) {
      const itemsCopy = items.slice();
      itemsCopy[itemIndex].count = count;

      setItems(itemsCopy);
    }
  }, []);

  return (
    <ul>
      {items.map(item => (
        <Item key={item.id} id={item.id} count={item.count} onUpdate={handleItemUpdate} items={items}/>
      ))}
    </ul>
  );
};
ReactDOM.render(<App />, document.getElementById("root"));