引言
在React生态中,状态管理一直是核心话题。随着React 18和Server Components的普及,传统方案如Redux面临挑战,而轻量级方案Zustand和Jotai逐渐成为新宠。本文将从实际开发角度,对比这三种方案在2025年的适用场景。
背景:为什么需要状态管理?
React的useState和useContext适合小型应用,但当状态需要跨组件共享、持久化或复杂异步逻辑时,专用状态管理库能显著提升开发效率。
方案概览
| 特性 | Zustand | Jotai | Redux Toolkit | |——|———|——-|—————| | 核心概念 | 单一store | 原子状态 | 单一store + reducer | | 学习曲线 | 低 | 中 | 高 | | 体积 | ~1KB | ~2KB | ~12KB | | TypeScript支持 | 优秀 | 良好 | 优秀 | | 中间件生态 | 有限 | 有限 | 丰富 |
实战对比
1. 计数器示例
Zustand
import { create } from 'zustand';
const useCounterStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}));
function Counter() {
const { count, increment, decrement } = useCounterStore();
return (
<div>
<h1>{count}</h1>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</div>
);
}
Jotai
import { atom, useAtom } from 'jotai';
const countAtom = atom(0);
function Counter() {
const [count, setCount] = useAtom(countAtom);
return (
<div>
<h1>{count}</h1>
<button onClick={() => setCount((c) => c + 1)}>+</button>
<button onClick={() => setCount((c) => c - 1)}>-</button>
</div>
);
}
Redux Toolkit
import { configureStore, createSlice } from '@reduxjs/toolkit';
import { Provider, useDispatch, useSelector } from 'react-redux';
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: (state) => { state.value += 1; },
decrement: (state) => { state.value -= 1; },
},
});
const store = configureStore({ reducer: counterSlice.reducer });
function Counter() {
const count = useSelector((state) => state.value);
const dispatch = useDispatch();
return (
<div>
<h1>{count}</h1>
<button onClick={() => dispatch(counterSlice.actions.increment())}>+</button>
<button onClick={() => dispatch(counterSlice.actions.decrement())}>-</button>
</div>
);
}
// App需包裹Provider
function App() {
return (
<Provider store={store}>
<Counter />
</Provider>
);
}
2. 异步操作(API请求)
Zustand
import { create } from 'zustand';
const useUserStore = create((set) => ({
user: null,
loading: false,
fetchUser: async (id) => {
set({ loading: true });
const res = await fetch(`/api/users/${id}`);
const user = await res.json();
set({ user, loading: false });
},
}));
Jotai (使用atomWithQuery)
import { atom } from 'jotai';
import { atomWithQuery } from 'jotai/query';
const userIdAtom = atom(1);
const userAtom = atomWithQuery((get) => ({
queryKey: ['user', get(userIdAtom)],
queryFn: async ({ queryKey: [, id] }) => {
const res = await fetch(`/api/users/${id}`);
return res.json();
},
}));
Redux Toolkit (createAsyncThunk)
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
export const fetchUser = createAsyncThunk('user/fetch', async (id) => {
const res = await fetch(`/api/users/${id}`);
return res.json();
});
const userSlice = createSlice({
name: 'user',
initialState: { user: null, status: 'idle' },
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchUser.pending, (state) => { state.status = 'loading'; })
.addCase(fetchUser.fulfilled, (state, action) => {
state.status = 'succeeded';
state.user = action.payload;
});
},
});
3. 性能优化
Zustand:自动细粒度更新
Zustand默认使用引用相等性检查,当组件只订阅部分状态时,不相关状态变化不会触发重渲染。
// 组件只订阅count,其他状态变化不会导致重渲染
const count = useCounterStore((state) => state.count);
Jotai:原子级更新
每个atom独立,只有依赖的atom变化才更新组件。
Redux Toolkit:需要手动选择
使用useSelector时需注意,默认浅比较,可结合createSelector做记忆化。
import { createSelector } from '@reduxjs/toolkit';
const selectUser = createSelector([state => state.user], user => user);
选型建议
- 小型项目或微前端:优先Zustand,零模板代码,API简洁,TypeScript友好。
- 需要精细控制渲染:Jotai的原子模型适合大型应用,避免不必要的重新渲染。
- 大型项目或团队协作:Redux Toolkit生态成熟,中间件丰富,但需注意学习成本。
- 服务端渲染(SSR):Zustand和Jotai支持良好,Redux需额外配置。
常见坑与最佳实践
- Zustand:避免在store外修改状态,使用
getState()需谨慎。
“javascript // 不推荐 const store = useCounterStore.getState(); store.count = 10; // 不会触发更新 “
- Jotai:派生原子注意依赖追踪,避免循环依赖。
- Redux Toolkit:不要直接修改state(Immer内部处理,但需遵循reducer纯函数原则)。
总结
没有银弹,选择取决于项目规模、团队经验和性能要求。Zustand适合快速开发,Jotai适合精细控制,Redux Toolkit适合大型企业级应用。建议小型项目先用Zustand,遇到性能瓶颈再考虑迁移。