作为一个React开发者,我经历过状态管理的"石器时代"。还记得刚开始用React时,我把所有状态都塞在组件里,结果组件变成了臃肿的"怪物"。后来我尝试了Redux,虽然解决了问题,但写起代码来像是在填表格——action、reducer、store配置一大堆,一个简单的计数器功能都要写半天。
直到我遇见了Zustand,这个德语单词意为"状态",它给我的开发体验带来了翻天覆地的变化。今天我想分享一下,为什么这个不到1KB的库能让我彻底放弃那些笨重的状态管理方案。
为什么选择Zustand?
刚开始接触React时,我和大多数新手一样,把状态一股脑塞进useState里。小项目还好,但随着功能增加,组件间的状态传递变成了噩梦——props drilling(属性钻取)让我的代码变成了"俄罗斯套娃"。
// 早期的组件状态管理噩梦
function Parent() {
const [count, setCount] = useState(0);
return <Child count={count} setCount={setCount} />;
}
function Child({ count, setCount }) {
return <GrandChild count={count} setCount={setCount} />;
}
function GrandChild({ count, setCount }) {
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
后来我学了Redux,确实解决了状态共享问题,但代价是大量的模板代码。创建一个简单的计数器,我需要定义action types、action creators、reducers,最后还要用Provider包裹整个应用。有时候我只是想存个小状态,却像是在为航天飞机写控制程序。
Context API稍微好点,但当我有多个context时,组件树会被一堆Provider包裹,性能也成了问题。就在我准备向状态管理复杂度低头时,Zustand出现了。
Zustand的核心思想很简单:给你一个创建store的hook,这个store可以在任何组件中使用,不需要Provider包裹。它融合了Redux的单一状态树思想和Hooks的简洁性,却没有任何繁琐的配置。
基础篇:创建和使用Store
计数器Store
让我们从一个经典的计数器例子开始。在项目中,我习惯将不同的store放在单独文件中:
// stores/countStore.js
import { create } from 'zustand';
export const useCountStore = create((set) => ({
count: 0,
inc: () => set((state) => ({ count: state.count + 1 })),
dec: () => set((state) => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }),
}));
在组件中使用这个store时,我们可以选择性地获取需要的状态和方法:
// components/Counter.js
import { useCountStore } from '../stores/countStore';
function Counter() {
const { count, inc, dec } = useCountStore();
return (
<div>
<button onClick={dec}>-</button>
<span>{count}</span>
<button onClick={inc}>+</button>
</div>
);
}
没有Provider,没有connect,没有mapStateToProps,就这么简单。更棒的是,Zustand会自动处理更新优化,只有用到的状态变化时组件才会重新渲染。
TodoList Store
对于更复杂的场景,比如Todo应用,我们可以这样组织store:
// stores/todoStore.js
import { create } from 'zustand';
export const useTodoStore = create((set) => ({
todos: [],
addTodo: (text) =>
set((state) => ({
todos: [...state.todos, { id: Date.now(), text, completed: false }],
})),
toggleTodo: (id) =>
set((state) => ({
todos: state.todos.map((todo) =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
),
})),
deleteTodo: (id) =>
set((state) => ({
todos: state.todos.filter((todo) => todo.id !== id),
})),
}));
对应的组件使用:
// components/TodoList.js
import { useState } from 'react';
import { useTodoStore } from '../stores/todoStore';
function TodoList() {
const [input, setInput] = useState('');
const { todos, addTodo, toggleTodo, deleteTodo } = useTodoStore();
const handleSubmit = (e) => {
e.preventDefault();
if (input.trim()) {
addTodo(input);
setInput('');
}
};
return (
<div>
<form onSubmit={handleSubmit}>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Add a new todo"
/>
<button type="submit">Add</button>
</form>
<ul>
{todos.map((todo) => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
/>
<span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
{todo.text}
</span>
<button onClick={() => deleteTodo(todo.id)}>Delete</button>
</li>
))}
</ul>
</div>
);
}
进阶篇:异步操作与复杂状态
Zustand真正强大的地方在于处理异步操作和复杂状态逻辑时的简洁性。
异步操作示例
在实际项目中,我们经常需要处理API请求。下面是一个用户数据获取的示例:
// stores/userStore.js
import { create } from 'zustand';
import axios from 'axios';
export const useUserStore = create((set) => ({
user: null,
loading: false,
error: null,
fetchUser: async (userId) => {
set({ loading: true, error: null });
try {
const response = await axios.get(`https://api.example.com/users/${userId}`);
set({ user: response.data, loading: false });
} catch (error) {
set({
error: error.response?.data?.message || error.message,
loading: false
});
}
},
updateUser: async (userId, updates) => {
set({ loading: true });
try {
const response = await axios.put(
`https://api.example.com/users/${userId}`,
updates,
{
headers: { 'Content-Type': 'application/json' },
}
);
set({ user: response.data, loading: false });
} catch (error) {
set({
error: error.response?.data?.message || error.message,
loading: false
});
}
},
}));
在组件中使用:
// components/UserProfile.js
import { useEffect } from 'react';
import { useUserStore } from '../stores/userStore';
function UserProfile({ userId }) {
const { user, loading, error, fetchUser } = useUserStore();
useEffect(() => {
fetchUser(userId);
}, [userId, fetchUser]);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
if (!user) return <div>No user found</div>;
return (
<div>
<h2>{user.name}</h2>
<p>Email: {user.email}</p>
{/* 其他用户信息 */}
</div>
);
}
派生状态与计算属性
Zustand支持在store中定义派生状态(类似于Vue的计算属性):
// stores/todoStore.js
export const useTodoStore = create((set, get) => ({
todos: [],
// ...其他action
// 派生状态:已完成todo数量
get completedCount() {
return get().todos.filter(todo => todo.completed).length;
},
// 派生状态:未完成todo数量
get activeCount() {
return get().todos.length - get().completedCount;
},
}));
高级技巧与最佳实践
1. 状态持久化
使用zustand/middleware可以轻松实现状态持久化:
// stores/authStore.js
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
export const useAuthStore = create(
persist(
(set) => ({
token: null,
user: null,
login: (token, user) => set({ token, user }),
logout: () => set({ token: null, user: null }),
}),
{
name: 'auth-storage', // localStorage的key
getStorage: () => localStorage, // 也可以使用sessionStorage
}
)
);
2. 使用Devtools调试
// stores/productStore.js
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
import axios from 'axios';
export const useProductStore = create(
devtools(
(set) => ({
products: [],
loading: false,
error: null,
fetchProducts: async () => {
set({ loading: true, error: null });
try {
const response = await axios.get('https://api.example.com/products');
set({
products: response.data,
loading: false
});
} catch (error) {
set({
loading: false,
error: error.response?.data?.message || error.message
});
}
},
// 可以添加更多产品相关操作
addProduct: async (newProduct) => {
set({ loading: true });
try {
const response = await axios.post(
'https://api.example.com/products',
newProduct
);
set(state => ({
products: [...state.products, response.data],
loading: false
}));
} catch (error) {
set({
loading: false,
error: error.response?.data?.message || error.message
});
}
}
}),
{ name: 'ProductStore' } // Devtools中显示的名称
)
);
3. 细粒度订阅优化性能
// components/TodoStats.js
import { useTodoStore } from '../stores/todoStore';
function TodoStats() {
// 只订阅completedCount和activeCount,当其他状态变化时不会重新渲染
const completedCount = useTodoStore((state) => state.completedCount);
const activeCount = useTodoStore((state) => state.activeCount);
return (
<div>
<p>Completed: {completedCount}</p>
<p>Active: {activeCount}</p>
</div>
);
}
4. 组合多个Store
对于大型项目,我们可以将store拆分为多个小store,然后在需要时组合使用:
// stores/index.js
export { useCountStore } from './countStore';
export { useTodoStore } from './todoStore';
export { useUserStore } from './userStore';
export { useAuthStore } from './authStore';
对比其他状态管理方案
与Redux对比
- 学习曲线:Zustand比Redux简单得多,不需要理解action、reducer、dispatch等概念
- 模板代码:Zustand几乎没有模板代码,Redux则需要大量样板代码
- 性能:两者都很优秀,但Zustand的细粒度订阅更直观
- 中间件:Redux中间件生态更丰富,但Zustand的中间件已经覆盖大部分常见需求
与Context API对比
- 渲染优化:Context API在值变化时会重新渲染所有消费者,Zustand只渲染使用变化状态的组件
- 使用便捷:Zustand不需要Provider层层包裹,使用更简单
- 适用场景:Context适合低频变化的主题/本地化等数据,Zustand适合高频变化的业务状态
实战建议
- 按功能划分store:不要创建巨型store,而是按业务功能划分
- 合理使用中间件:持久化、devtools等中间件可以显著提升开发体验
- 避免过度使用:简单的组件状态仍应使用useState,只有共享状态才放入store
- 类型安全:使用TypeScript可以获得更好的开发体验:
// stores/countStore.ts
interface CountState {
count: number;
inc: () => void;
dec: () => void;
reset: () => void;
}
export const useCountStore = create<CountState>((set) => ({
count: 0,
inc: () => set((state) => ({ count: state.count + 1 })),
dec: () => set((state) => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }),
}));
结语
经过多个项目的实践,Zustand已经成为我React工具箱中的必备品。它完美平衡了简单性和功能性,让状态管理从痛苦变成了乐趣。 当然,不同的场景也要选择不同的工具,如果是大型项目,还是老老实实选择redux吧!