react useCallback 闭包陷阱

1. 为什么需要useCallback

  • 在使用useCallback 和 useMemo钩子之前,如果每次修改parent的count,都会导致child重新渲染
const Child = (props) => {
  console.log('Child render');
  return (
    
) } const parent = () => { const [count, setCount] = useState(1); const cb = () => { console.log(count); }; return (
parent
) }

2. 场景复现

  • 函数组件中,一般 useCallback 和 useMemo都是搭配使用, 模拟componentShouldUpdate钩子,避免不必要的渲染。优化之后,我们就发现,在parent中,不断的setCount修改状态,也不会触发Child组件的更新。彷佛解决了 父函数组件更新状态,导致子组件的不必要更新问题。但是当点击子组件的button时,就会发现,打印的是 count的初始值 1,这也就是 useCallback的闭包陷阱,不能拿到最新的state。
const Child = (props) => {
  console.log('Child render');
  return (
    
) } const parent = () => { const [count, setCount] = useState(1); const cb = React.useCallback(() => { console.log(count); }, []); const ChildMemo = React.useMemo(() => , [cb]); return (
parent count : {count} {ChildMemo}
) }

3. 解决方式有两种: 两种方式都是将callback保存在一个引用对象中,区别就在于,方式2不需要去修改子组件,但是也增加了理解的成本

  • 放弃useCallback, 使用 useRef钩子,用ref.current去引用箭头函数。使用useRef还需要对子组件回调的调用修改一下。
const Child = (props) => {
  console.log('Child render');
  return (
    
) } const parent = () => { const [count, setCount] = useState(1); const cbRef = React.useRef(null); cbRef.current = () => { console.log(count); }; const ChildMemo = React.useMemo( () => , [] ); return (
parent count : {count} {ChildMemo}
) }
  • 自定义一个hook,使用 useCallback搭配useRef
const useImmediateValue = (cb) => {
  const ref = useRef(cb);
  ref.current = cb;
  const val = useCallback(() => {
     ref.current();
  }, []);
  return val;
}

const Child = (props) => {
  console.log('Child render');
  return (
    
) } const parent = () => { const [count, setCount] = useState(1); const cb = useImmediateValue(() => { console.log(count); }) const ChildMemo = React.useMemo( () => , [] ); return (
parent count : {count} {ChildMemo}
) }

你可能感兴趣的:(react useCallback 闭包陷阱)