多API场景下Redux Toolkit的data reducers应当如何结构化?
Redux Toolkit 多API场景下的状态结构化方案对比与最佳实践
方案优劣判断
你当前使用的按数据领域拆分slice的方案是符合Redux设计规范的正确方向,第二种按请求动作拆分slice的写法属于典型的反模式,核心问题如下:
- 违背Redux「同领域数据聚合管理」的核心原则,同一份产品数据会散落在
getProduct、createProduct等多个slice中,数据同步需要跨slice分发action,维护成本极高 - 状态结构冗余,同一份数据的相关状态分散在多个节点,组件取数、处理请求状态需要引入多份状态,代码冗余度大幅上升
- 浪费Redux Toolkit的设计能力,createSlice本身的设计目标就是聚合同一份数据的读写逻辑、请求处理逻辑,按请求拆slice完全不符合该设计目标
现有方案的优化方向
你现有的拆分逻辑只需要在状态结构、逻辑聚合上做优化即可满足大部分业务需求,以productSlice为例,参考实现如下:
状态结构设计
// 产品领域的状态类型定义 interface ProductState { // 核心数据做归一化存储,按id索引,查询、更新性能远高于数组存储 data: Record<number | string, Product> // 列表页关联的分页、筛选参数 listMeta: { page: number pageSize: number total: number filters: Record<string, any> } // 所有请求的loading状态聚合存储,无需单独拆分slice loading: { getList: boolean getDetail: boolean create: boolean update: boolean delete: boolean } // 所有请求的错误信息聚合存储 error: { getList: string | null getDetail: string | null create: string | null update: string | null delete: string | null } }
slice逻辑实现
同一个领域的所有异步请求thunk、reducer逻辑都聚合在同一个slice文件中:
// 产品相关的所有异步请求thunk统一定义 export const getProductList = createAsyncThunk( 'product/getList', async (params: ListParams) => { return await request.get('/products', { params }) } ) export const createProduct = createAsyncThunk( 'product/create', async (data: Omit<Product, 'id'>) => { return await request.post('/products', data) } ) export const updateProduct = createAsyncThunk( 'product/update', async ({ id, data }: { id: number | string; data: Partial<Product> }) => { return await request.put(`/products/${id}`, data) } ) export const deleteProduct = createAsyncThunk( 'product/delete', async (id: number | string) => { return await request.delete(`/products/${id}`) } ) // slice初始化与逻辑处理 const productSlice = createSlice({ name: 'product', initialState: { data: {}, listMeta: { page: 1, pageSize: 10, total: 0, filters: {} }, loading: { getList: false, getDetail: false, create: false, update: false, delete: false }, error: { getList: null, getDetail: null, create: null, update: null, delete: null } } as ProductState, reducers: { // 同领域的同步reducer逻辑统一在这里定义 clearProductList: (state) => { state.data = {} state.listMeta = { page: 1, pageSize: 10, total: 0, filters: {} } } }, extraReducers: (builder) => { // 统一处理所有异步请求的状态更新 builder // 获取产品列表逻辑 .addCase(getProductList.pending, (state) => { state.loading.getList = true state.error.getList = null }) .addCase(getProductList.fulfilled, (state, action) => { state.loading.getList = false // 归一化存储返回的列表数据 action.payload.data.forEach((item: Product) => { state.data[item.id] = item }) state.listMeta = action.payload.meta }) .addCase(getProductList.rejected, (state, action) => { state.loading.getList = false state.error.getList = action.error.message || '获取产品列表失败' }) // 新增产品逻辑 .addCase(createProduct.pending, (state) => { state.loading.create = true state.error.create = null }) .addCase(createProduct.fulfilled, (state, action) => { state.loading.create = false // 新增数据直接存入归一化对象即可 state.data[action.payload.id] = action.payload }) .addCase(createProduct.rejected, (state, action) => { state.loading.create = false state.error.create = action.error.message || '创建产品失败' }) // 剩余更新、删除等请求的处理逻辑按照上述格式补充即可 } }) export const { clearProductList } = productSlice.actions export default productSlice.reducer
多API场景进阶方案
如果你的项目接口数量多、CRUD逻辑重复度高,可以直接使用Redux Toolkit官方内置的RTK Query工具,它自动实现了数据缓存、请求状态管理、重复请求去重、数据失效更新等通用能力,无需手动编写大量thunk和reducer逻辑,更适合多接口的中大型项目。
内容的提问来源于stack exchange,提问作者Eunsim Kang
相关产品推荐
相关产品推荐

