6、Redux 架构最佳实践:如何构建可维护、可扩展的状态体系?

144 阅读3分钟

✍️ 大型项目实战经验总结

🎯 目标:不是教你用 Redux,而是教你如何架好一个能撑 3 年不塌的 Redux 状态系统

🧠 关键词:模块化、可组合、解耦化、Redux Toolkit、异步策略、团队协作


💥 初学 Redux 时的典型错误架构

  1. 所有 action 放一个文件里:actions.js
  2. 所有 reducer 混在一起:reducer.js
  3. 状态结构像「垃圾堆」一样扁平混乱
  4. 异步写死在组件里,用大量 useEffect + dispatch
  5. 命名随缘、action type 到处 hardcode

结果就是:

  • 状态不能复用;
  • 模块不能解耦;
  • 异步处理乱七八糟;
  • onboarding 一个新人,他三天都看不懂状态流程。

✅ 最佳实践一:状态架构的基本原则

Redux 架构设计的第一步:不要“按文件功能”分,而是“按领域模块”分。

🧱 推荐的目录结构(Domain-Centric):

src/
├── store/
│   ├── index.ts           // 创建和导出 store
│   └── slices/
│       ├── userSlice.ts   // 用户模块
│       ├── postSlice.ts   // 帖子模块
│       └── commentSlice.ts
├── features/
│   ├── user/
│   │   └── UserProfile.tsx
│   └── post/
│       └── PostList.tsx

每个 slice

  • 只负责自己的状态;
  • 导出自己的 actions;
  • 提供 selector 封装;
  • 和 UI 解耦(不直接操作组件)。

📦 最佳实践二:使用 Redux Toolkit 统一规范

我们在内部项目中不再手写 action type 和 reducer,而是统一使用 createSlicecreateAsyncThunk

🚀 createSlice 示例:

const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment(state) {
      state.value += 1
    },
    set(state, action: PayloadAction<number>) {
      state.value = action.payload
    }
  }
})

🎯 好处:

  • 自动生成 action creator;
  • 支持 Immer 写法(写“可变”代码,执行“不可变”逻辑);
  • 和 TS 类型推导天然结合;
  • 默认内聚,代码维护成本低。

🔄 最佳实践三:异步操作用 createAsyncThunk 管理

export const fetchUser = createAsyncThunk(
  'user/fetch',
  async (id: string) => {
    const res = await api.getUser(id)
    return res.data
  }
)

然后在 extraReducers 中处理 loading / success / error:

extraReducers: builder => {
  builder
    .addCase(fetchUser.pending, state => {
      state.loading = true
    })
    .addCase(fetchUser.fulfilled, (state, action) => {
      state.profile = action.payload
      state.loading = false
    })
}

你再也不用手写 loading、error 管理逻辑了,Toolkit 自动分发 3 个 action:

user/fetch/pending
user/fetch/fulfilled
user/fetch/rejected

🧠 最佳实践四:Selector 是状态系统的“接口层”

直接在组件中写 state.user.data.xxx 会让组件依赖状态结构细节,耦合性高。

正确做法:

// userSlice.ts
export const selectUserName = (state: RootState) => state.user.name

组件中只使用:

const name = useSelector(selectUserName)

Selector 是状态的“公开 API”,防止内部结构泄漏,未来 refactor 更安心。


🔒 最佳实践五:store 解耦,支持动态注入 reducer(微前端/插件场景)

Redux 支持动态注册 reducer:

store.injectReducer('chat', chatReducer)

适用于:

  • 微前端应用按需加载;
  • 页面级模块异步挂载;
  • 插件系统动态注册状态域。

Toolkit 也支持动态组合 reducer:

const rootReducer = combineReducers({
  user: userReducer,
  ...(extraReducers || {})
})

⚙️ 最佳实践六:统一中间件策略处理日志、权限、埋点

不要在组件里写这些逻辑,推荐中间件封装:

const loggerMiddleware = store => next => action => {
  console.log('[Action]', action.type)
  return next(action)
}

组合使用:

configureStore({
  reducer,
  middleware: getDefaultMiddleware().concat(loggerMiddleware)
})

统一处理:

  • 埋点;
  • 权限校验;
  • 接口限频;
  • 请求重试;
  • 错误拦截。

📐 最佳实践七:团队协作建议(TS 项目)

  • 所有 slice 都导出自己的 actions + selectors
  • 禁止跨模块 dispatch 其他模块 action
  • 定义统一的 RootState 和 AppDispatch 类型:
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch

并封装 hook:

export const useAppDispatch = () => useDispatch<AppDispatch>()
export const useAppSelector = useSelector.withTypes<RootState>()

📊 总结:Redux 不是写法问题,而是架构问题

你不用 Toolkit 时,Redux 像 C 语言 —— 灵活但容易踩雷; 用了 Toolkit + 拆分最佳实践,Redux 就像 Rust —— 安全、有约束、强工程力。

优秀的 Redux 架构应满足:

维度表现
可维护性slice 明确,低耦合
可扩展性支持动态注入,异步加载
可测试性reducer/selector 易单测
可协作性action/selector 可文档化
性能表现connect/useSelector + 缓存

⏭️ 下一篇预告

我们将进入实战写代码阶段,下一篇: 第7篇:《Redux Toolkit 源码解构与工程设计精髓》

你将看到:createSlice、createAsyncThunk 背后的自动代码生成机制、Immer 集成方式、TS 类型推导策略——一切都不再神秘。