✍️ 项目血泪经验
🎯 目标:为你和团队建立一套可维护、可扩展、无痛升级的 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 自动处理 |