You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用闭包实现Reselect记忆化选择器是否存在性能损耗?

Reselect选择器记忆化的性能疑问

我有一个关于Reselect选择器记忆化的问题。根据我对Reselect文档的理解,以下是实现带参数记忆化选择器的推荐且正确的方式:

const selectOrdersByCustomer = createSelector(
    [
        state => state.orders,
        (state, customerId) => customerId,
    ],
    (orders, customerId) => {
        return orders.filter(order => order.customerId === customerId);
    }
);
const orders = useSelector(state => selectOrdersByCustomer(state, customerId));

我的一位同事提出了该选择器的另一种实现方式:

const selectOrdersByCustomer = customerId => createSelector(
    state => state.orders,
    orders => {
        return orders.filter(order => order.customerId === customerId);
    }
);
const orders = useSelector(selectOrdersByCustomer(customerId));

(我对代码进行了简化,实际项目中的实现要复杂得多)我在使用该选择器的组件中添加了console.count('counter');进行测试,发现两种实现触发的重渲染次数相同。我的问题是:第二种实现相比第一种是否存在性能损耗?


回答

第二种实现确实存在隐性的性能损耗,具体体现在这几个方面:

  • 每次渲染都创建新的选择器实例
    第二种写法里,selectOrdersByCustomer(customerId)会在组件每次渲染时执行,每次都会调用createSelector生成一个全新的选择器对象。Reselect的记忆化是绑定在选择器实例上的——新实例意味着之前缓存的计算结果完全失效,相当于每次都在重新创建一个无历史缓存的选择器。
    对比第一种写法:选择器是全局唯一的单实例,它会缓存所有不同customerId和orders组合的计算结果,只要这两个参数不变,就会直接返回缓存值。

  • useSelector的执行成本更高
    useSelector默认会对前后两次返回的结果做引用相等性比较。第二种写法每次传给useSelector的都是新生成的选择器函数(因为每次调用selectOrdersByCustomer都会生成新的createSelector结果),这会导致useSelector每次都必须执行这个新函数,再去比较返回的orders数组。
    而第一种写法里,传给useSelector的是一个稳定的箭头函数(只要组件不被销毁重建,这个箭头函数是同一个引用),useSelector可以更高效地复用之前的判断逻辑。

  • 缓存粒度缺失导致重复计算
    第一种写法的选择器会缓存所有customerId对应的结果,比如用户切换不同客户ID后再切回来,只要orders没变化,就能直接复用缓存。但第二种写法里,每次切换customerId都会生成新的选择器,之前客户ID对应的缓存会被直接丢弃,下次再切回来时必须重新计算。

你测试时重渲染次数相同,是因为console.count只统计了组件的重渲染——组件重渲染的触发条件是useSelector返回的orders引用变化。但第二种写法在每次渲染时都额外增加了创建选择器的开销,同时丢失了跨customerId的缓存能力,这些是你没测到的隐性性能损耗。

内容的提问来源于stack exchange,提问作者frehder

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 15:10:21