🧨 Redux 番外篇 6:工程实战集锦——企业项目中常见的 7 个反模式与 7 个最佳实践

100 阅读3分钟

✍️ 项目血泪经验

🎯 目标:为你和团队建立一套可维护、可扩展、无痛升级的 Redux 使用规范

🧠 关键词:反模式、最佳实践、状态规范、异步处理、性能优化、代码整洁


🧨 常见反模式一览(别踩坑)

编号反模式名称问题简述
Action 类型写成字符串易拼写错误、无类型提示、不易重构
所有状态集中在一个大 store状态难以维护、修改容易影响全局
Action 中混杂副作用破坏可测试性、调试困难
dispatch 写满组件里无法复用、难以测试、逻辑分散
把所有接口数据都存 Redux缺乏必要性,反而增加复杂度
每个 action 都写 reducer样板多、代码膨胀
selector 写死在组件内无法共享,缺乏缓存与抽象层

✅ 最佳实践 1:使用 Redux Toolkit 替代原始写法

🧠 经典写法:

dispatch({ type: 'ADD_TODO', payload: todo })

✅ 推荐写法(RTK):

dispatch(addTodo(todo))
  • 自动生成 action type
  • 自动补全 payload 类型
  • 与 createSlice 联动

✅ 最佳实践 2:每个领域一个 Slice,状态解耦

结构参考:

/src
├── store/
│   ├── index.ts        // configureStore
│   └── slices/
│       ├── userSlice.ts
│       ├── productSlice.ts
│       └── cartSlice.ts

每个 slice 独立维护自己的:

  • initialState
  • reducers
  • action creator
  • selector

利于维护与协作。


✅ 最佳实践 3:异步逻辑统一用 createAsyncThunk

🧨 反模式:

dispatch({ type: 'FETCH_USER_PENDING' })
try {
  const res = await fetch('/api/user')
  dispatch({ type: 'FETCH_USER_SUCCESS', payload: res })
} catch (err) {
  dispatch({ type: 'FETCH_USER_ERROR' })
}

✅ 推荐:

const fetchUser = createAsyncThunk('user/fetch', async () => {
  const res = await fetch('/api/user')
  return await res.json()
})
dispatch(fetchUser())

统一结构 + 自动处理三种状态。


✅ 最佳实践 4:封装 selector + 使用 reselect 进行性能优化

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

组件内使用:

const userName = useSelector(selectUserName)

🧠 搭配 reselect 做计算型状态缓存:

import { createSelector } from 'reselect'

export const selectCartTotal = createSelector(
  (state) => state.cart.items,
  (items) => items.reduce((sum, item) => sum + item.price, 0)
)

✅ 最佳实践 5:中间件处理副作用,不写进 reducer

反模式:

const reducer = (state, action) => {
  if (action.type === 'LOGIN_SUCCESS') {
    saveToken(action.payload.token) // 副作用
  }
}

✅ 推荐:

使用 thunk/middleware 处理:

dispatch(login(user)) // 内部封装副作用逻辑

reducer 永远只做纯函数状态转换,易测试。


✅ 最佳实践 6:组件层 dispatch 封装成 hook

反模式:

useEffect(() => {
  dispatch(fetchUser())
}, [])

✅ 推荐:

const useInitUser = () => {
  const dispatch = useAppDispatch()
  useEffect(() => {
    dispatch(fetchUser())
  }, [])
}

配合 useAppSelector/useAppDispatch 封装统一用法。


✅ 最佳实践 7:状态结构设计为可扩展、持久化友好型

设计 tips:

  • 所有 slice 状态尽量平铺(避免深嵌套结构)
  • 默认状态保持最小结构(避免不必要冗余)
  • 尽量支持序列化,兼容 persist / devtools
  • 异步状态单独提取(如 loading、error)

例:

interface ProductState {
  data: Product[]
  loading: boolean
  error: string | null
}

🔧 Redux 项目模板推荐结构

/src
├── store/
│   ├── index.ts          // configureStore
│   └── slices/
│       ├── userSlice.ts
│       ├── ...
│
├── selectors/            // 可选
│   ├── userSelectors.ts
│
├── hooks/
│   ├── useAppDispatch.ts
│   ├── useInitUser.ts

配合 TypeScript,自动类型推导、严格检查、代码补全效果最佳。


🧑‍💻 实战总结

分类错误做法正确做法
状态结构所有状态写一起每个领域一个 slice
action 使用写死字符串使用 RTK 自动生成
dispatch 写法分散在组件中封装成 hooks
副作用写进 reducer使用 thunk/middleware 处理
选择器写死在组件使用 selector + reselect 封装
代码维护无模块组织模块化目录、TS 类型联动
异步处理手动 dispatch 三态使用 createAsyncThunk 自动处理