基于Feature架构风格重构Redux的Reducer与Action
Next.js + Redux Toolkit 功能架构重构问题与优化方案
背景
我正在学习Next.js,已基于Redux Toolkit集成Redux,希望改用功能驱动架构(Feature-Based Architecture),将每个功能封装在独立文件夹中,欢迎各位提出增删改建议。
当前目录结构
components public pages |-accounts | |-index.tsx |-_app.tsx |-index.tsx store |-account | |-accountsApi.ts | |-actions.ts | |-index.ts | |-reducer.ts | |-types.ts |-index.ts styles package.json ...
核心问题与代码优化
1. 让Login Action接收凭证对象
当前组件中尝试分发带凭证对象的login action,但action未定义payload类型,需修改以下文件:
修改 store/account/actions.ts
明确指定login action的payload类型:
import { createAction } from '@reduxjs/toolkit' import { AccountActionTypes, LoginInputModel } from './types' const create = createAction(AccountActionTypes.CREATE) // 绑定payload类型为登录凭证模型 const login = createAction<LoginInputModel>(AccountActionTypes.LOGIN) const logout = createAction(AccountActionTypes.LOGOUT) export const actionCreators = { create, login, logout, };
修改 store/account/reducer.ts
更新login case的payload类型,确保类型安全:
import { createReducer, PayloadAction } from '@reduxjs/toolkit' import { actionCreators } from './actions'; import { Account, AccountState, LoginInputModel } from './types'; export const initialState: AccountState = { isAuthenticated: false, token: '', status: 'idle', accounts: [] as Account[], } export const reducer = createReducer(initialState, (builder) => { builder // 明确payload类型,后续可结合接口返回调整状态更新逻辑 .addCase(actionCreators.login, (state, action: PayloadAction<LoginInputModel>) => { // 示例:假设接口返回token后更新认证状态 state.isAuthenticated = true; state.token = action.payload.token; }) .addCase(actionCreators.logout, (state) => { state.isAuthenticated = false; state.token = ''; }) .addCase(actionCreators.create, (state, action: PayloadAction<Account>) => { state.accounts = [...state.accounts, action.payload]; }) .addDefaultCase((state) => { state.isAuthenticated = false; state.token = ''; }) })
修改后组件中dispatch(accoutActions.login(obj))即可正常传递凭证对象,且类型校验生效。
2. 解决Slice中Reducer类型错误
当前store/account/index.ts中reducers: reducer报错,原因是createSlice的reducers字段需要CaseReducers对象(键为reducer名称、值为对应处理函数),而非createReducer生成的完整Reducer函数。
重构 store/account/index.ts(推荐方案)
将状态、reducers逻辑整合到slice中,贴合RTK最佳实践:
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit' import { CreateAccountInputModel, LoginInputModel, AccountState } from './types' import type { AppState } from '..' import { createAccount, loginAccount, logoutAccount } from './accountsAPI' // 初始状态 const initialState: AccountState = { isAuthenticated: false, token: '', status: 'idle', accounts: [], } // 异步Thunk:创建账户 export const createAsync = createAsyncThunk( 'account/create', async (objModel: CreateAccountInputModel) => { const response = await createAccount(objModel) return response.data } ) // 异步Thunk:登录 export const loginAsync = createAsyncThunk( 'account/login', async (credentials: LoginInputModel) => { const response = await loginAccount(credentials) return response.data } ) // 创建Slice export const accountSlice = createSlice({ name: 'account', initialState, reducers: { logout: (state) => { state.isAuthenticated = false state.token = '' }, }, extraReducers: (builder) => { builder // 创建账户异步状态处理 .addCase(createAsync.pending, (state) => { state.status = 'loading' }) .addCase(createAsync.fulfilled, (state, action) => { state.status = 'idle' state.accounts.push(action.payload) }) // 登录异步状态处理 .addCase(loginAsync.pending, (state) => { state.status = 'loading' }) .addCase(loginAsync.fulfilled, (state, action) => { state.status = 'idle' state.isAuthenticated = true state.token = action.payload.token }) }, }) // 导出同步actions export const { logout } = accountSlice.actions // 导出异步actions export { createAsync, loginAsync } // 导出状态选择器 export const isAuthenticated = (state: AppState) => state.account.isAuthenticated // 导出reducer export default accountSlice.reducer
清理冗余文件
此时可删除单独的actions.ts和reducer.ts,所有逻辑统一到slice中,彻底解决类型错误。
3. Reducer中状态更新方式的疑问
在RTK中,由于内置了Immer库,你有两种合法的状态更新方式:
- 直接修改state(推荐):
state.isAuthenticated = true,Immer会自动转换为不可变更新逻辑 - 返回新状态对象:
return { ...state, isAuthenticated: true },这种方式完全支持,但直接修改更简洁
两种方式效果一致,可根据个人习惯选择。
整体架构与优化建议
- 功能架构规范:当前
store/account的结构符合功能架构,可继续保持,每个功能模块独立封装api、slice、types。 - API层优化:后续用Axios单例重构时,可在
store/account下创建axiosInstance.ts,统一管理该模块的请求配置。 - 组件数据获取优化:
pages/accounts/index.tsx中的账户数据建议通过Redux选择器获取,而非页面props传递,更符合Next.js + Redux的实践。 - 异步逻辑统一:将登出等异步操作也用
createAsyncThunk实现,统一管理loading、success、error状态。
来源标注
内容的提问来源于stack exchange,提问作者Sachihiro
相关产品推荐
相关产品推荐

