You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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 },这种方式完全支持,但直接修改更简洁

两种方式效果一致,可根据个人习惯选择。

整体架构与优化建议

  1. 功能架构规范:当前store/account的结构符合功能架构,可继续保持,每个功能模块独立封装api、slice、types。
  2. API层优化:后续用Axios单例重构时,可在store/account下创建axiosInstance.ts,统一管理该模块的请求配置。
  3. 组件数据获取优化:pages/accounts/index.tsx中的账户数据建议通过Redux选择器获取,而非页面props传递,更符合Next.js + Redux的实践。
  4. 异步逻辑统一:将登出等异步操作也用createAsyncThunk实现,统一管理loading、success、error状态。

来源标注

内容的提问来源于stack exchange,提问作者Sachihiro

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 13:55:17