React useCallback依赖变化时未更新,捕获错误依赖值问题
你的核心问题在于没搞清楚React渲染快照的特性,以及effect和useCallback的生命周期关联。
先拆解你的代码执行流程:
- 组件首次渲染:
- state初始值为0
useCallback生成一个捕获了state=0的回调函数callback- 执行依赖为空数组的
useEffect:调用setState(1)触发重渲染,同时这个effect的清理函数(return的函数)引用的是本次渲染生成的callback(也就是捕获state=0的那个)
- 组件重渲染(state变为1):
- 生成新的
callback,捕获了state=1 - 但因为原effect的依赖是空数组,所以不会重新执行这个effect,原effect的清理函数还是引用旧的
callback
- 生成新的
- 组件卸载时:
- 执行原effect的清理函数,调用的是第一次渲染时的
callback,自然打印0
- 执行原effect的清理函数,调用的是第一次渲染时的
关于useCallback和普通函数的区别
你觉得两种写法结果相同,是因为普通函数在每次渲染时都会重新创建,而useCallback在依赖不变时会复用之前的函数。但在你的场景里,首次渲染时不管是普通函数还是useCallback生成的函数,都是捕获state=0的,而effect的清理函数引用的都是首次渲染的那个函数,所以结果一致。
如果把effect的依赖加上callback,区别就体现出来了:
React.useEffect(() => { setState(1); return () => { callback(); }; }, [callback]);
这时,当state变为1,useCallback会生成新的callback,effect会重新执行,新的清理函数会引用捕获state=1的callback,卸载时就会打印1。而普通函数的话,每次渲染都会重新创建,effect会每次都执行,可能会导致不必要的重复执行,这就是useCallback用来优化的场景——减少依赖它的effect或子组件的重渲染。
为什么你觉得useCallback没捕获正确值?
这是误解,useCallback确实正确捕获了它创建时的依赖值。问题出在effect的清理函数属于创建它的那次渲染,它不会自动更新到最新的callback,除非你把callback加入effect的依赖数组。
不用ref或函数式setState的解决方案
如果你想直接访问最新的state,同时不想用ref或函数式setState,可以让effect依赖callback,这样每次state变化,callback更新,effect就会重新执行,清理函数也会更新为引用最新的callback:
const [state, setState] = React.useState(0); const callback = React.useCallback(() => { console.log(state); }, [state]); React.useEffect(() => { setState(1); return () => { callback(); }; }, [callback]); // 这里加入callback作为依赖
这样组件卸载时,清理函数调用的是最新的callback,会打印1。
内容的提问来源于stack exchange,提问作者FreeDom Sy

