React Native中Redux Toolkit跨Slice状态赋值优化咨询
背景说明
我在Redux Toolkit的Store中维护了两个Slice:
RootMissions Slice
type RootMissionStoreType = { key: string; title: string; missions: Record<string, MissionStoreType>; }; type RootMissionsSliceState = { rootMissions: Record<string, RootMissionStoreType>; }; const initialState: RootMissionsSliceState = { rootMissions: {}, }; export const RootMissionsSlice = createSlice({ name: SLICES_NAMES.rootMissions, initialState, reducers: { // 已有部分action实现 }, });
Missions Slice
type MissionsSliceState = { missions: Record<string, MissionStoreType>; focusedMission: string | null; }; const initialState: MissionsSliceState = { missions: {}, focusedMission: null, }; export const MissionsSlice = createSlice({ name: SLICES_NAMES.missions, initialState, reducers: { // 已有部分action实现 }, });
可以看到,MissionsSlice的missions字段类型与RootMissionsSlice中rootMissions下的missions字段类型完全一致。我需要将MissionsSlice的missions数据存入RootMissionsSlice的rootMissions[id]中(id由Action的payload指定)。
当前实现及问题
目前我在组件中采用以下写法:
const allMissions = useAppSelector(MissionsSelector); //... const handleBackPress = () => { dispatch( setRootMissionMissions({ key: route.params.key, missions: allMissions }) ); };
但该方案存在明显问题:每次Missions数据发生变化,都会触发当前组件的重新渲染,我需要一种能避免这类额外渲染的优化方案。
优化方案
1. 精准选择器 + useCallback包裹回调
首先优化数据选择逻辑,只订阅需要的missions字段而非整个MissionsSlice;同时用useCallback包裹回调函数,减少不必要的函数引用更新:
import { useCallback } from 'react'; import { useDispatch, useAppSelector } from 'react-redux'; // 精准选择器:仅获取missions数据 const missionsSelector = (state) => state.missions.missions; const YourComponent = ({ route }) => { const missions = useAppSelector(missionsSelector); const dispatch = useDispatch(); // 用useCallback固定回调引用,仅依赖必要参数 const handleBackPress = useCallback(() => { dispatch( setRootMissionMissions({ key: route.params.key, missions }) ); }, [route.params.key, missions, dispatch]); // 组件其他逻辑 };
这样只有当missions或route.params.key变化时,handleBackPress才会更新,有效减少组件重渲染次数。
2. Redux层面直接处理数据同步(推荐)
将数据同步逻辑从组件移到Redux内部,避免组件订阅不必要的状态:
方式A:在RootMissions的Reducer中直接读取全局状态
Redux Toolkit的Reducer允许访问整个Store状态,因此可以修改RootMissionsSlice的action,让payload仅传入目标id,在Reducer中直接获取MissionsSlice的missions数据:
// 在RootMissionsSlice的reducers中新增action setRootMissionMissionsById: (state, action: PayloadAction<string>) => { const rootMissionKey = action.payload; // 直接从全局state读取missions数据 const globalMissions = (state as RootState).missions.missions; if (!state.rootMissions[rootMissionKey]) { state.rootMissions[rootMissionKey] = { key: rootMissionKey, title: '', missions: {} }; } state.rootMissions[rootMissionKey].missions = globalMissions; },
之后组件只需触发action,无需订阅missions状态:
const handleBackPress = useCallback(() => { dispatch(setRootMissionMissionsById(route.params.key)); }, [route.params.key, dispatch]);
组件完全不需要订阅missions数据,自然不会因missions变化触发重渲染。
方式B:使用监听中间件自动同步
如果需要在特定Action触发时自动同步数据,可以使用Redux Toolkit的createListenerMiddleware:
import { createListenerMiddleware } from '@reduxjs/toolkit'; // 创建监听中间件 const listenerMiddleware = createListenerMiddleware(); // 添加监听规则:当MissionsSlice的更新Action触发时同步数据 listenerMiddleware.startListening({ actionCreator: MissionsSlice.actions.updateMissions, // 替换为实际更新missions的Action effect: async (action, listenerApi) => { // 根据业务场景获取需要同步的rootMissionKey,比如从路由状态中读取 const rootMissionKey = listenerApi.getState().someRouteState.activeRootKey; if (rootMissionKey) { const missions = listenerApi.getState().missions.missions; listenerApi.dispatch( RootMissionsSlice.actions.setRootMissionMissions({ key: rootMissionKey, missions }) ); } }, }); // 配置Store时添加中间件 export const store = configureStore({ reducer: { rootMissions: RootMissionsSlice.reducer, missions: MissionsSlice.reducer, }, middleware: (getDefaultMiddleware) => getDefaultMiddleware().prepend(listenerMiddleware.middleware), });
这种方式下,组件无需处理同步逻辑,完全由Redux中间件自动完成数据同步。
3. 使用shallowEqual减少无意义重渲染
如果必须订阅missions数据,可以用shallowEqual作为useSelector的比较函数,避免因对象引用变化触发的不必要重渲染:
import { shallowEqual } from 'react-redux'; const allMissions = useAppSelector(MissionsSelector, shallowEqual);
shallowEqual会对比对象的浅层属性,只要内容未变化,即使引用更新也会判定为相等,从而阻止组件重渲染。
内容的提问来源于stack exchange,提问作者AvishayIDF

