
前阵子在修一个 bug,一个弹窗怎么都关不了。
我打开代码一看,吓到了。
用户点了"关闭"按钮,链路是这样的:
onClick → dispatch(closeModal)
↓
Redux action 触发
↓
Saga 拦截住了
↓
调了个根本不需要的接口
↓
Service 更新全局状态
↓
整个 Redux store 重新渲染
↓
弹窗才关上
本来 200ms 的事,整成了 1s 多。而且那个接口调用根本不必要。
我就在想:为什么一个简单的 UI 交互,要走这么复杂的流程?
后来我翻了翻项目代码,发现这不是个案。
我问过一个做中后台的哥们儿,他们项目 Redux 代码 3000+ 行。我说,这里面有多少真的需要全局?
他想了想说:"也就 30%?其他的都是……下拉菜单、表单、弹窗这些东西。"
我又看了另一个项目。为了维护一个"用户正在输入"的状态,写了:
就为了 true 和 false。
用 useState 呢?
const [isTyping, setIsTyping] = useState(false)
完事。一行代码。
这就是问题的所在。
我仔细想过,真正需要放在全局状态里的,其实就这几种:
1. 用户认证信息
这个确实要全局。任何页面、任何组件都可能需要知道"当前用户是谁"、"有没有登录"。
2. App 级别的配置
比如主题色、语言切换。用户设置一次,整个应用都要反应。
3. 复杂的业务数据依赖
比如电商平台的购物车。商品价格、用户积分、优惠券、运费、税金……这些数据之间有复杂的计算关系,一个变了其他都要联动。这种情况确实需要一个统一的地方管理。
4. 跨越很多层级的数据共享
比如某个数据需要从根组件传到第 7 层子组件。每一层都要透传,很烦。这时全局状态有意义。
除了这些呢?
坦白说,90% 的其他状态都不需要。
下拉菜单的开关 —— 不需要全局
// ❌ 很多人的做法
// Redux action
exportconst TOGGLE_DROPDOWN = 'TOGGLE_DROPDOWN'
const toggleDropdown = () => ({ type: TOGGLE_DROPDOWN })
// Reducer
const dropdownReducer = (state = false, action) => {
if (action.type === TOGGLE_DROPDOWN) return !state
return state
}
// 组件使用
const isOpen = useSelector(state => state.dropdown.isOpen)
const dispatch = useDispatch()
<button onClick={() => dispatch(toggleDropdown())}>
{isOpen ? '关闭' : '打开'}
</button>
需要改 3 个文件,写 20+ 行代码。
// ✅ 用 useState
const [isOpen, setIsOpen] = useState(false)
<button onClick={() => setIsOpen(!isOpen)}>
{isOpen ? '关闭' : '打开'}
</button>
两行搞定。
用户登录信息 —— 需要全局
这个不能只放在一个组件里,因为:
用 Redux 可以,但现在有更轻的方案:
// 用 Context + useState(不需要 Redux)
const AuthContext = createContext()
function AuthProvider({ children }) {
const [user, setUser] = useState(null)
const [loading, setLoading] = useState(false)
const login = async (email, password) => {
setLoading(true)
try {
const res = await api.login(email, password)
setUser(res.user)
localStorage.setItem('token', res.token)
} finally {
setLoading(false)
}
}
const logout = () => {
setUser(null)
localStorage.removeItem('token')
}
return (
<AuthContext.Provider value={{ user, loading, login, logout }}>
{children}
</AuthContext.Provider>
)
}
// 用的时候
const { user, login } = useContext(AuthContext)
没有 action、没有 reducer、没有 middleware。就是 JavaScript 对象和函数。简洁很多。
有。但用的场景越来越具体了。
我见过这么用的:
场景 1:大型金融平台
实时行情数据、委托单、持仓、收益率……这些数据高频变化,互相关联。Redux 配合 Saga 处理异步,确实能管好。
场景 2:超大型团队的中后台系统
一个团队 100+ 人,多个业务线共享一个平台。Redux 的规范性和可追溯性有价值。
场景 3:需要时间旅行调试的应用
比如实时协作编辑、画布应用。需要回放每一步操作。Redux DevTools 的能力在这里能体现。
但是……一般的 CRUD 应用、普通的中后台系统、小团队的项目? Redux 往往是过度设计。
我参与过一个电商平台的重构。原来是 Redux 全家桶。
项目状态管理:
总共 6000+ 行。
我们重构时做了这样的分层:
第 1 层:UI 状态 → useState
表单输入、下拉菜单、弹窗、Tab 切换……用本地 state。这些数据不需要离开组件。
function ProductList() {
const [currentTab, setCurrentTab] = useState('all')
const [sortBy, setSortBy] = useState('newest')
const [filterOpen, setFilterOpen] = useState(false)
// 这些状态只在这个组件里用
return (...)
}
第 2 层:共享业务逻辑 → 自定义 Hook
比如获取用户的购物历史、生成订单这种,多个组件需要,但逻辑相同。提取成 Hook:
function useUserOrders(userId) {
const [orders, setOrders] = useState([])
const [loading, setLoading] = useState(true)
useEffect(() => {
api.getUserOrders(userId).then(data => {
setOrders(data)
setLoading(false)
})
}, [userId])
return { orders, loading }
}
// 多个组件都可以用
function OrderList() {
const { orders, loading } = useUserOrders(userId)
return (...)
}
第 3 层:真正的全局状态 → 轻量级方案
用户信息、购物车、主题……这些才用全局管理。我们选了 Zustand:
import create from'zustand'
const useStore = create((set) => ({
// 用户信息
user: null,
setUser: (user) =>set({ user }),
// 购物车
cart: [],
addToCart: (product) =>set(state => ({
cart: [...state.cart, product]
})),
removeFromCart: (productId) =>set(state => ({
cart: state.cart.filter(p => p.id !== productId)
})),
// 主题
theme: 'light',
toggleTheme: () =>set(state => ({
theme: state.theme === 'light' ? 'dark' : 'light'
}))
}))
// 使用
const user = useStore(state => state.user)
const addToCart = useStore(state => state.addToCart)
为什么选 Zustand?
第 4 层:服务端数据 → React Query
这是最重要的一点。很多项目在 Redux 里维护了一套"前端数据库"——把所有后端数据都缓存在 state 里。这带来了:
用 React Query 就对了:
import { useQuery, useMutation } from'@tanstack/react-query'
// 获取商品列表
const { data: products, isLoading } = useQuery({
queryKey: ['products'],
queryFn: () => api.getProducts()
})
// 更新购物车
const { mutate: updateCart } = useMutation({
mutationFn: (cartData) => api.updateCart(cartData),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['cart'] })
}
})
React Query 替你处理缓存、重试、失败处理……你就写业务逻辑。
为什么这么快?因为代码简单了,不需要花时间理解 Redux 的概念。看代码就能懂。
不用急着全删。但下次写功能前,问自己三个问题:
1. 这个状态只有当前组件用吗?
→ 用 useState
2. 多个组件需要共享,但逻辑相同?
→ 提取成自定义 Hook
3. 真的是 App 全局共享的状态,而且不会频繁变化?
→ 用 Context + useState,或者 Zustand
4. 这是来自服务器的数据,而且会变化?
→ 用 React Query
只有 当这些都不适合,你才需要考虑 Redux。
大多数项目,你根本走不到那一步。
如果你的 Redux 代码满足这些条件,可能是时候考虑简化了:
return { ...state, key: value }写代码不是比谁用的技术栈多,而是比谁的代码改起来最顺手。
我见过最顺畅的项目,从来不是最"企业级"的那个。就是该用啥用啥,没有多余的复杂度。
你现在的项目里,有多少 Redux 代码其实是"我们都这么写,所以就这么写"的?
评论区分享一下。或者说说你踩过的状态管理的坑?