Redux Toolkit:是否需在useSelector中配合使用useCallback
1. 不使用proxy-memoize时,useSelector通常不需要配合useCallback
Redux的useSelector内部逻辑很明确:每次组件渲染时,它会调用传入的selector函数拿到结果,然后对当前结果和上一次的结果做浅比较——只要结果没变化,就不会触发组件重新渲染。
像你写的这种简单取值的selector:
const todo = useSelector((state) => state.todos[props.id])
虽然每次渲染时这个箭头函数都是新的引用,但useSelector根本不关心selector函数本身的引用是否变化,它只看返回值的浅比较结果。所以这种场景下,完全没必要套useCallback。
只有当你的selector涉及大量重复计算,或者依赖外部变量需要稳定缓存计算结果时,Redux官方推荐用createSelector(来自@reduxjs/toolkit或reselect)来创建记忆化的selector,而不是用useCallback包裹。
2. proxy-memoize要求配合useCallback的原因
proxy-memoize的核心是给selector函数添加结果缓存——它会记住之前传入的state和对应的返回值,当state中相关部分没变化时,直接返回缓存的结果,避免重复计算。
但问题在于:如果每次组件渲染都重新调用memoize(state => ({ … })),就会生成一个全新的带缓存的函数实例,之前积累的缓存会被直接丢弃,等于白做了记忆化优化。
这时候useCallback的作用就是固定memoize后的selector函数引用,保证每次渲染时传给useSelector的是同一个函数,这样proxy-memoize内部的缓存才能持续生效,真正起到减少重复计算的作用。
比如错误写法(缓存完全失效):
// 无效:每次渲染都重新生成memoized函数,缓存无法复用 useSelector(memoize(state => ({ todo: state.todos[props.id] })))
正确写法(缓存稳定生效):
// 有效:用useCallback固定函数引用,缓存能持续复用 useSelector(useCallback(memoize(state => ({ todo: state.todos[props.id] })), [props.id]))
这里还要加上[props.id]作为依赖数组,当props.id变化时,才重新生成memoized函数,保证selector能拿到最新的数据。
内容的提问来源于stack exchange,提问作者tobiasBora

