假设我的组件很慢,记住它很有意义,例如

const SlowButton = ({onClick}) => {
    // make some heat
    const value = (function f(n) { return n < 2 ? Math.random() : f(n-1) + f(n-2)})(32)|0;
    return <button onClick={() => onClick(value)}>{value}</button>
}
const MemoButton = React.memo(SlowButton);


如果在类似这样的组件中使用MemoButton:

const Counter = () => {
    const [clicks, setClicks ] = useState(0);
    const handleClick = () => {
        setClicks(clicks + 1);
    }
    return <div>
            <div>{clicks}</div>
            <MemoButton onClick={handleClick} />
        </div>
}


然后,MemoButton仍然每次都重新渲染,因为onClick属性是每个渲染的新函数。解决这个问题很容易:

const Counter2 = () => {
    const [clicks, setClicks] = useState(0);
    const handleClick = useCallback(() => {
        setClicks(c => c + 1);
    },[]);

    return <div>
            <div>{clicks}</div>
            <MemoButton onClick={handleClick} />
        </div>
}


上面的方法工作正常,但组件更复杂,效果不佳:

const CounterGroup = () => {
    const numButtons = 3;
    const [message, setMessage] = useState('button X clicked');
    const handleClick = (val, idx) => setMessage(`button ${idx} clicked: ${val}`);
    return <div>
        <div>{message}</div>
        {Array(numButtons).fill(0).map((_, i) =>
            <MemoButton key={i} onClick={(v) => handleClick(v,i)} />)
        }
        </div>
}


在上面的代码中,(v) => handleClick(i,v)始终将是一个新的函数引用。有没有好的技术来防止更改每个渲染?

一种可能性是忽略对“ on ...”道具的更改,但这只会带来新的问题:

const compareValuePropsOnly = (prev, next) => Object.entries(prev).every(
    ([k, v]) => k.substr(0, 2) === "on" || v === next[k]
);
const MemoOnlyValsButton = React.memo(SlowButton, compareValuePropsOnly);


这是一个codeandbox版本:
https://codesandbox.io/s/memoization-function-reference-changes-9c1fy

最佳答案

一种解决方案是让您的SlowButton传递i值,而不是从循环中获取它,并记住handleClick

const SlowButton = ({onClick, i}) => {
    // make some heat
    const value = (function f(n) { return n < 2 ? Math.random() : f(n-1) + f(n-2)})(32)|0;
    return <button onClick={() => onClick(value, i)}>{value}</button>
}
const MemoButton = React.memo(SlowButton);

const CounterGroup = () => {
    const numButtons = 3;
    const [message, setMessage] = useState('button X clicked');
    const handleClick = React.useCallback((val, idx) => setMessage(`button ${idx} clicked: ${val}`), []);
    return <div>
        <div>{message}</div>
        {Array(numButtons).fill(0).map((_, i) =>
            <MemoButton key={i} i={i} onClick={handleClick} />)
        }
        </div>
}



另一种方法是在React.memo中排除“ onClick”道具(由于它是事件处理程序,因此不会影响组件的外观)。

  const MemoButton = React.memo(SlowButton, (props1, props2) => {
    // assume that SlowButton have some props that affect it's UI
    // we don't compare onClick because it won't affect UI
    return props1.someUIProps === props2.someUIProps;
})


或者,您可以使用useEventCallback挂钩记录功能。在这种情况下,您需要在CounterGroupMemoButton之间创建一个组件

const useEventCallback = (callback) => {
  // store latest callback
  const ref = useRef(callback);
  useEffect(() => ref.current = callback);

  // memoize the callback to maintain its identity
  return useCallback((...args) => ref.current(...args), []);
}
const FastButton = ({onClick} => {
 // FastButton will be re-rendered multiple times, but since memoOnClick have same identity
 // on sub sequence re-renders, MemoButton should not be re-rendered
 const memoOnClick = useEventCallback(onClick);

 return <MemoButton onClick={memoOnClick} />
});


const CounterGroup = () => {
    const numButtons = 3;
    const [message, setMessage] = useState('button X clicked');
    const handleClick = (val, idx) => setMessage(`button ${idx} clicked: ${val}`);
    return <div>
        <div>{message}</div>
        {Array(numButtons).fill(0).map((_, i) =>
            <FastButton key={i} onClick={(v) => handleClick(v,i)} />)
        }
        </div>
}

09-28 03:22