首页 > 解决方案 > Reactjs 使用 useCallback 和 memo 防止不需要的渲染

问题描述

objects我有一个被调用的数组data。我循环这个数组并渲染Counter组件。计数器值的递增和递减作为 props 传递给组件。

但是,如果我更改一个组件中的值,其他两个组件也会重新渲染。这是不需要的。如何防止这种行为?我试过了memouseCallback但似乎没有正确实施。

计数器.js

import React, { useEffect } from "react";

    const Counter = ({ value, onDecrement, onIncrement, id }) => {
      useEffect(() => {
        console.log("Function updated!", id);
      }, [onDecrement, onIncrement]);
      return (
        <div>
          {value}
          <button onClick={() => onDecrement(id)}>-</button>
          <button onClick={() => onIncrement(id)}>+</button>
        </div>
      );
    };
    
    export default React.memo(Counter);

主页.js

import React, { useState, useCallback } from "react";
import Counter from "../components/Counter";

export default function Home() {
  const [data, setData] = useState([
    {
      id: 1,
      value: 0,
    },
    {
      id: 2,
      value: 0,
    },
    {
      id: 3,
      value: 0,
    },
  ]);

  const onIncrement = useCallback(
    (id) => {
      setData((e) =>
        e.map((record) => {
          if (record.id === id) {
            record.value += 1;
          }
          return record;
        })
      );
    },
    [data]
  );

  const onDecrement = useCallback(
    (id) => {
      setData((e) =>
        e.map((record) => {
          if (record.id === id) {
            record.value -= 1;
          }
          return record;
        })
      );
    },
    [data]
  );

  return (
    <div>
      <h1>Home</h1>
      {data.map((e) => {
        return (
          <Counter
            value={e.value}
            onDecrement={onDecrement}
            onIncrement={onIncrement}
            id={e.id}
          />
        );
      })}
    </div>
  );
}

标签: javascriptreactjsreact-hooks

解决方案


我怀疑useCallback&useMemo在这种情况下没有帮助,因为您正在渲染中运行内联函数:

{data.map(e => <Counter ...>)}

此函数将始终返回一个新数组,并且该组件将始终与前一个不同。

为了解决这个问题,我认为您需要记住该渲染函数,而不是 Counter 组件。

这是一个带有 useRef 的简单记忆渲染函数:


  // inside of a React component

  const cacheRef = useRef({})

  const renderCounters = (data) => {
    let results = []
    data.forEach(e => {
      const key = `${e.id}-${e.value}`
      const component = cacheRef.current[key] || <Counter
            value={e.value}
            key={e.id}
            onDecrement={onDecrement}
            onIncrement={onIncrement}
            id={e.id}
          />
      results.push(component)
      cacheRef.current[key] = component
    })

    return results
  }

  return (
    <div>
      <h1>Home</h1>
      {renderCounters(data)}
    </div>
  );

在下面的代码框中,只有被点击的组件记录了它的 ID: https ://codesandbox.io/s/vibrant-wildflower-0djo4?file=/src/App.js

免责声明:使用此实现,组件仅在其数据值更改时才会重新呈现。其他道具(例如递增/递减回调)不会触发更改。也没有办法清除缓存。

Memoize 也在用内存换取性能——有时这并不值得。如果可能有数千个计数器,则有更好的优化,即更改 UI 设计、虚拟化列表等。

我确定有一种方法可以使用useMemo/React.memo但我不熟悉它


推荐阅读