1.useCallback的闭包陷阱
const add = () => {
const [count, setCount] = setState(0)
const increament = useCallback(function () {//传入一个回调函数,每次都会重新定义传入的回调函数
setCount(count + 1)
}, [])
return (<div>
<h2>count</h2>
<button onClick={increament}>increament</button>
</div>)
}
简单的按钮点击count加1,但是此案例在点击第一次后count会加1,后面点击count保持不变。
原因:
useCallback函数每次都会重新定义传入的回调函数,在堆中重新开辟一块空间放置回调函数内容,但是useCallback函数具有记忆对比能力,如果依赖项不变,那么它将返回该回调函数的 memoized 版本(该案例中,依赖项为空数组,所以返回给increament的是堆中同一个空间的函数的引用地址,即 点击前的increament===点击后的increament),不同于普通的函数,每次都会重新定义。increament仅在某个依赖项改变时才会更新,所以后续调用increament时,setCount(count+1)中的count取的一直是第一次传入useCallback回调定义时的count。
2.性能优化
结论:
当需要将一个函数传递给子组件,最好使用useCallback进行性能优化,将优化后的函数传递给子组件。子组件要在React.memo()中,才能做到性能优化
原因:
就是因为不希望在修改父组件的其他内容时,影响到与之内容不相关的子组件的重新渲染,因为组件的props改变时,组件将会重新渲染,而每次父组件重新渲染时,如果是普通函数传入子组件的,那么父组件重新渲染时,函数将会重新定义改变,子组件接收到的props就会改变,就会引起子组件的重新渲染。
如果是useCallback定义的回调,除非是依赖项改变,否则其回调始终相同,传递给子组件,就不会因为父组件修改非依赖项之外的内容而导致子组件重新渲染的事情发生。
const ChangeName = React.memo((props) => {
const { changeName } = props
return (
<div>
<button onClick={changeName}></button>
</div>
)
})
const add = () => {
const [count, setCount] = setState(0)
const [name, setName] = setState('kangkang')
const increament = useCallback(function () {//传入一个回调函数
setCount(count + 1)
}, [count])
const changeName = useCallback(() => {
setName('mike')
})
return (<div>
<h2>count</h2>
h2
<button onClick={increament}>increament</button>
<h2>name</h2>
<ChangeName changeName={changeName}></ChangeName>
</div>)
}
该案例中,修改count时,不会引起ChangeName子组件的重新渲染(子组件要在React.memo()中)。
3.解决闭包陷阱
// 通过useRef解决闭包陷阱
const countRef = useRef()
countRef.current = count
const increment = useCallback(() => {
setCount(countRef.current + 1)
}, [])