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

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;
  }
);

问题根源

  1. 包装函数导致选择器实例不固定:selectWalletWithTotalBalance每次调用都会返回一个新的匿名函数,React-Redux的useSelector会将这个函数视为新的依赖,因此每次状态变更都会重新执行选择器,触发重渲染。
  2. 新对象引用变化未被正确忽略:即使选择器缓存生效,返回的新对象引用不同,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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 14:37:21