react中多个页面,数据相互依赖reducer问题及解决
更新时间:2025年12月20日 15:52:02 作者:初遇你时动了情
文章介绍了一个电商商品管理页面的复杂状态管理场景,使用useReducer或useImmerReducer来集中管理状态,避免组件间直接监听,简化联动逻辑,防止死循环,并便于维护和扩展
场景
典型的电商商品管理页面复杂状态场景,涉及多个模块数据联动、动态生成 SKU、属性和额外参数注入等。关键点是 避免组件之间直接相互监听,保持逻辑集中化
页面模块
- 基本信息
- 商品名称
- 分类 / 平台分类
- 详细信息(富文本/描述)
- 规格名称、规格值
- SKU 列表:基于规格动态生成
- SKU 可能包含额外字段(如价格、库存、额外参数)
- 动态数据依赖
切换平台分类 → 获取动态属性、额外参数 → 注入到 SKU
修改规格名称 → 重新生成 SKU
核心问题:
- SKU 生成依赖规格数据、额外参数
- 额外参数依赖平台分类
- 数据变化涉及多个模块联动,容易出现重复计算或死循环
useReducer(或者 useImmerReducer) 比直接在组件里用一堆 useState + useEffect 更适合,原因如下:
- 集中管理状态:所有页面状态都在一个地方管理,组件只负责渲染和触发操作。
- 统一处理联动逻辑:SKU 生成、额外参数注入、动态属性拉取都在 reducer 内完成,不会散落在各组件的 useEffect 里。
- 避免死循环:不需要组件间相互监听状态变化,更新逻辑集中在 reducer 内部。
- 便于维护和扩展:新增字段或联动规则,只需改 reducer,不用改每个组件
状态设计
import { useReducer } from 'react';
import produce from 'immer';
interface ProductState {
basicInfo: {
name: string;
categoryId: number;
platformCategoryId: number;
description: string;
};
specs: {
name: string;
values: string[];
}[];
dynamicAttributes: Record<string, any>; // 平台分类动态属性
extraParams: Record<string, any>; // 平台分类额外参数
skuList: SKU[];
}
interface SKU {
id?: string;
specCombination: string[]; // 每个SKU对应的规格值组合
price: number;
stock: number;
extraParams?: Record<string, any>;
}
const initialState: ProductState = {
basicInfo: {
name: '',
categoryId: 0,
platformCategoryId: 0,
description: ''
},
specs: [],
dynamicAttributes: {},
extraParams: {},
skuList: []
};
Reducer 设计(包含联动逻辑)
type Action =
| { type: 'UPDATE_BASIC_INFO'; payload: Partial<ProductState['basicInfo']> }
| { type: 'UPDATE_SPEC'; payload: { index: number; spec: Partial<ProductState['specs'][0]> } }
| { type: 'SET_PLATFORM_CATEGORY'; payload: { platformCategoryId: number; dynamicAttributes: any; extraParams: any } }
| { type: 'UPDATE_SKU'; payload: SKU[] };
function generateSKUList(specs: ProductState['specs'], extraParams: ProductState['extraParams']): SKU[] {
// 简化示例:生成规格组合的笛卡尔积
if (specs.length === 0) return [];
function cartesian(arrays: string[][]): string[][] {
return arrays.reduce<string[][]>(
(a, b) => a.flatMap(d => b.map(e => [...d, e])),
[[]]
);
}
const specValues = specs.map(s => s.values.length ? s.values : ['']);
const combinations = cartesian(specValues);
return combinations.map(comb => ({
specCombination: comb,
price: 0,
stock: 0,
extraParams: { ...extraParams }
}));
}
function productReducer(state: ProductState, action: Action): ProductState {
switch (action.type) {
case 'UPDATE_BASIC_INFO':
return { ...state, basicInfo: { ...state.basicInfo, ...action.payload } };
case 'UPDATE_SPEC':
return produce(state, draft => {
draft.specs[action.payload.index] = { ...draft.specs[action.payload.index], ...action.payload.spec };
draft.skuList = generateSKUList(draft.specs, draft.extraParams);
});
case 'SET_PLATFORM_CATEGORY':
return produce(state, draft => {
draft.basicInfo.platformCategoryId = action.payload.platformCategoryId;
draft.dynamicAttributes = action.payload.dynamicAttributes;
draft.extraParams = action.payload.extraParams;
draft.skuList = generateSKUList(draft.specs, draft.extraParams);
});
case 'UPDATE_SKU':
return { ...state, skuList: action.payload };
default:
return state;
}
}
自定义 Hook 封装
export function useProductManager() {
const [state, dispatch] = useReducer(productReducer, initialState);
const updateBasicInfo = (payload: Partial<ProductState['basicInfo']>) => {
dispatch({ type: 'UPDATE_BASIC_INFO', payload });
};
const updateSpec = (index: number, spec: Partial<ProductState['specs'][0]>) => {
dispatch({ type: 'UPDATE_SPEC', payload: { index, spec } });
};
const setPlatformCategory = async (platformCategoryId: number) => {
const dynamicAttributes = await fetchDynamicAttributes(platformCategoryId);
const extraParams = await fetchExtraParams(platformCategoryId);
dispatch({
type: 'SET_PLATFORM_CATEGORY',
payload: { platformCategoryId, dynamicAttributes, extraParams }
});
};
const updateSKU = (skuList: SKU[]) => {
dispatch({ type: 'UPDATE_SKU', payload: skuList });
};
return { state, updateBasicInfo, updateSpec, setPlatformCategory, updateSKU };
}
- 组件只调用 updateXXX 或 setPlatformCategory
- SKU 联动逻辑和额外参数注入都在 reducer 内完成
- 避免在组件里写大量 useEffect
组件使用示例
const ProductPage = () => {
const { state, updateBasicInfo, updateSpec, setPlatformCategory, updateSKU } = useProductManager();
return (
<div>
<BasicInfoForm info={state.basicInfo} onChange={updateBasicInfo} />
<SpecsEditor specs={state.specs} onChange={updateSpec} />
<SKUList skuList={state.skuList} onChange={updateSKU} />
<PlatformCategorySelector
selected={state.basicInfo.platformCategoryId}
onChange={setPlatformCategory}
/>
</div>
);
};
- 各组件只关注自己的 slice 数据
- 不用管其他模块数据的联动
- 所有复杂联动逻辑在 hook/reducer 内集中处理

总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
相关文章
React immer与Redux Toolkit使用教程详解
这篇文章主要介绍了React中immer与Redux Toolkit的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧2022-10-10


最新评论