react中多个页面,数据相互依赖reducer解决方案
场景
典型的电商商品管理页面复杂状态场景,涉及多个模块数据联动、动态生成 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} /><PlatformCategorySelectorselected={state.basicInfo.platformCategoryId}onChange={setPlatformCategory}/></div>);
};
-
各组件只关注自己的 slice 数据
-
不用管其他模块数据的联动
-
所有复杂联动逻辑在 hook/reducer 内集中处理