1. 为什么须要useCallback

  • 在应用useCallback 和 useMemo钩子之前,如果每次批改parent的count,都会导致child从新渲染
const Child = (props) => {  console.log('Child render');  return (    <div>      <button onClick={props.clickCallback}>Child click</button>    </div>  )}const parent = () => {  const [count, setCount] = useState(1);  const cb = () => {    console.log(count);  };    return (    <div>      parent      <button onClick={() => setCount()}>click</button>      <Child clickCallback={cb} />    </div>  )}

2. 场景复现

  • 函数组件中,个别 useCallback 和 useMemo都是搭配应用, 模仿componentShouldUpdate钩子,防止不必要的渲染。优化之后,咱们就发现,在parent中,一直的setCount批改状态,也不会触发Child组件的更新。彷佛解决了 父函数组件更新状态,导致子组件的不必要更新问题。然而当点击子组件的button时,就会发现,打印的是 count的初始值 1,这也就是 useCallback的闭包陷阱,不能拿到最新的state。
const Child = (props) => {  console.log('Child render');  return (    <div>      <button onClick={props.clickCallback}>Child click</button>    </div>  )}const parent = () => {  const [count, setCount] = useState(1);    const cb = React.useCallback(() => {    console.log(count);  }, []);  const ChildMemo = React.useMemo(() => <Child clickCallback={cb} />, [cb]);  return (    <div style={{       width: '100vw', height: '100vh',       justifyContent: 'center', alignItems: 'center',       display: 'flex',       flexDirection: 'column' }}     >      parent count : {count}      <button onClick={() => setCount(count + 1)}>click</button>      {ChildMemo}    </div>  )}

3. 解决形式有两种: 两种形式都是将callback保留在一个援用对象中,区别就在于,形式2不须要去批改子组件,然而也减少了了解的老本

  • 放弃useCallback, 应用 useRef钩子,用ref.current去援用箭头函数。应用useRef还须要对子组件回调的调用批改一下。
const Child = (props) => {  console.log('Child render');  return (    <div>      <button onClick={() => {        props.callbackRef.current();           }}       >Child click</button>    </div>  )}const parent = () => {  const [count, setCount] = useState(1);    const cbRef = React.useRef(null);  cbRef.current = () => {    console.log(count);  };  const ChildMemo = React.useMemo(    () => <Child callbackRef={cbRef} />,    []  );  return (    <div style={{       width: '100vw', height: '100vh',       justifyContent: 'center', alignItems: 'center',       display: 'flex',       flexDirection: 'column' }}     >      parent count : {count}      <button onClick={() => setCount(count + 1)}>click</button>      {ChildMemo}    </div>  )}
  • 自定义一个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 (    <div>      <button onClick={props.clickCallback}>Child click</button>    </div>  )}const parent = () => {  const [count, setCount] = useState(1);    const cb = useImmediateValue(() => {    console.log(count);  })  const ChildMemo = React.useMemo(    () => <Child clickCallback={cb} />,    []  );  return (    <div style={{       width: '100vw', height: '100vh',       justifyContent: 'center', alignItems: 'center',       display: 'flex',       flexDirection: 'column' }}     >      parent count : {count}      <button onClick={() => setCount(count + 1)}>click</button>      {ChildMemo}    </div>  )}