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> )}