Redux Toolkit Reselect任意状态变更后触发重渲染问题求解
解决Reselect选择器触发不必要组件重渲染的问题
我尝试用Reselect创建一个选择器,从Redux状态中选取两个变量并合并返回,但遇到了任意状态变更都会触发组件重渲染的问题。即使将基础选择器改写为Reselect选择器,问题依然存在。我需要保留创建新对象的逻辑,求可行解决方案。
原实现代码:
import { createSelector } from 'reselect'; import { RootState } from 'store/store.ts'; const selectWallet = (state: RootState, id: string | null) => { if (!id) return undefined; return state.wallets.list[id] || undefined; }; const selectTotal = (state: RootState, id: string | null) => { if (!id) return 0; return state.transactions.walletsTransactionsTotals[id] || 0; }; export const reselectWalletWithTotalBalance = createSelector([selectWallet, selectTotal], (wallet, total) => { if (!wallet) { return undefined; } if (!total) { return wallet; } return { ...wallet, balance: wallet.balance + total }; }); export const selectWalletWithTotalBalance = (id: string | null) => { return (state: RootState) => reselectWalletWithTotalBalance(state, id); };
尝试过的修改(无效):
const selectWallet = createSelector( [(state: RootState) => state.wallets.list, (_, id: string | null) => id], (walletsList, id): WalletType | undefined => { if (!id) return undefined; return walletsList[id] || undefined; } );
问题根源
- 包装函数导致选择器实例不固定:
selectWalletWithTotalBalance每次调用都会返回一个新的匿名函数,React-Redux的useSelector会将这个函数视为新的依赖,因此每次状态变更都会重新执行选择器,触发重渲染。 - 新对象引用变化未被正确忽略:即使选择器缓存生效,返回的新对象引用不同,
useSelector默认的引用比较会认为值已变化,导致重渲染。
解决方案
方案一:为每个ID创建独立的选择器实例(推荐)
利用工厂函数+缓存,为每个id生成独立的Reselect选择器实例,确保Reselect的缓存机制正常工作:
import { createSelector } from 'reselect'; import { RootState } from 'store/store.ts'; // 基础选择器:改为带参数的Reselect选择器 const selectWallet = createSelector( [(state: RootState) => state.wallets.list, (_, id: string | null) => id], (walletsList, id) => { if (!id) return undefined; return walletsList[id] || undefined; } ); const selectTotal = createSelector( [(state: RootState) => state.transactions.walletsTransactionsTotals, (_, id: string | null) => id], (totals, id) => { if (!id) return 0; return totals[id] || 0; } ); // 工厂函数:创建单个选择器实例 const makeWalletWithTotalSelector = () => { return createSelector( [selectWallet, selectTotal], (wallet, total) => { if (!wallet) return undefined; if (!total) return wallet; return { ...wallet, balance: wallet.balance + total }; } ); }; // 缓存每个ID对应的选择器实例 const selectorCache = new Map<string | null, ReturnType<typeof makeWalletWithTotalSelector>>(); export const selectWalletWithTotalBalance = (id: string | null) => { if (!selectorCache.has(id)) { selectorCache.set(id, makeWalletWithTotalSelector()); } return selectorCache.get(id)!; };
在组件中使用:
import { useSelector } from 'react-redux'; import { selectWalletWithTotalBalance } from './selectors'; const WalletComponent = ({ id }) => { const walletWithTotal = useSelector((state) => selectWalletWithTotalBalance(id)(state)); // ...组件逻辑 };
方案二:配合shallowEqual进行内容比较
如果不想维护选择器缓存,可以直接使用原始选择器,在useSelector中传入shallowEqual忽略引用变化,只比较对象内容:
import { useSelector, shallowEqual } from 'react-redux'; import { reselectWalletWithTotalBalance } from './selectors'; const WalletComponent = ({ id }) => { const walletWithTotal = useSelector( (state) => reselectWalletWithTotalBalance(state, id), shallowEqual // 浅比较对象内容,而非引用 ); // ...组件逻辑 };
如果对象嵌套较深,可以使用深比较函数(比如lodash的isEqual)替代shallowEqual。
说明
- 方案一利用Reselect的内置缓存,只有当对应ID的
wallet或total变化时才会重新计算,性能更优。 - 方案二更简洁,适合简单场景,但每次状态变更都会执行选择器函数,仅通过比较结果避免重渲染。
内容的提问来源于stack exchange,提问作者Arthur
相关产品推荐
相关产品推荐

