Appearance
Zustand入门
概述
Zustand是一个小巧、快速、可扩展的状态管理库,采用简化的Flux原则。它提供了基于Hooks的API,没有样板代码,使用起来非常简单直观。
为什么选择Zustand
Zustand的优势
- 简单直观:API简洁,学习曲线平缓
- 体积小巧:压缩后仅约1KB
- 无样板代码:不需要Provider包裹
- TypeScript友好:原生支持TypeScript
- 性能优秀:基于选择器的精确订阅
- 中间件支持:内置多种中间件
- React外使用:可在React组件外部使用
与其他方案对比
jsx
// Redux - 样板代码多
const INCREMENT = 'INCREMENT';
const increment = () => ({ type: INCREMENT });
const reducer = (state = { count: 0 }, action) => {
switch (action.type) {
case INCREMENT:
return { ...state, count: state.count + 1 };
default:
return state;
}
};
// Context - 需要Provider包裹
const CountContext = createContext();
function CountProvider({ children }) {
const [count, setCount] = useState(0);
return (
<CountContext.Provider value={{ count, setCount }}>
{children}
</CountContext.Provider>
);
}
// Zustand - 简洁直观
import { create } from 'zustand';
const useStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 }))
}));安装和基础设置
安装
bash
# npm
npm install zustand
# yarn
yarn add zustand
# pnpm
pnpm add zustand创建第一个Store
jsx
import { create } from 'zustand';
// 创建store
const useCounterStore = create((set) => ({
// 状态
count: 0,
// 操作
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
reset: () => set({ count: 0 })
}));
// 在组件中使用
function Counter() {
const count = useCounterStore((state) => state.count);
const increment = useCounterStore((state) => state.increment);
const decrement = useCounterStore((state) => state.decrement);
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</div>
);
}核心概念
1. State(状态)
jsx
const useStore = create((set) => ({
// 基本类型
count: 0,
name: 'John',
isActive: true,
// 对象
user: {
id: 1,
name: 'Alice',
email: 'alice@example.com'
},
// 数组
items: [],
todos: [
{ id: 1, text: 'Learn Zustand', completed: false }
],
// 复杂嵌套
config: {
theme: 'light',
settings: {
notifications: true,
language: 'zh-CN'
}
}
}));2. Actions(操作)
jsx
const useStore = create((set, get) => ({
count: 0,
users: [],
// 基础操作
increment: () => set((state) => ({ count: state.count + 1 })),
// 直接设置
reset: () => set({ count: 0 }),
// 使用get获取当前状态
incrementIfEven: () => {
const count = get().count;
if (count % 2 === 0) {
set({ count: count + 1 });
}
},
// 异步操作
fetchUsers: async () => {
const response = await fetch('/api/users');
const users = await response.json();
set({ users });
},
// 批量更新
updateMultiple: () => set({
count: 0,
name: 'Updated',
isActive: false
})
}));3. Selectors(选择器)
jsx
const useStore = create((set) => ({
user: { name: 'Alice', age: 30 },
posts: [],
settings: { theme: 'light' }
}));
function Component() {
// 选择单个属性
const userName = useStore((state) => state.user.name);
// 选择多个属性
const { user, settings } = useStore((state) => ({
user: state.user,
settings: state.settings
}));
// 使用计算属性
const userAge = useStore((state) => state.user.age);
const isAdult = userAge >= 18;
// 选择器函数
const postCount = useStore((state) => state.posts.length);
}4. 订阅机制
jsx
const useStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 }))
}));
// 在组件中订阅
function Counter() {
// 只在count变化时重渲染
const count = useStore((state) => state.count);
// 不会引起重渲染(只获取函数)
const increment = useStore((state) => state.increment);
return (
<div>
<p>{count}</p>
<button onClick={increment}>Increment</button>
</div>
);
}
// 在React外部订阅
const unsubscribe = useStore.subscribe(
(state) => console.log('State changed:', state)
);
// 取消订阅
unsubscribe();基础用法示例
示例1:计数器应用
jsx
import { create } from 'zustand';
const useCounterStore = create((set, get) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
incrementBy: (value) => set((state) => ({ count: state.count + value })),
reset: () => set({ count: 0 }),
// 使用get
double: () => {
const currentCount = get().count;
set({ count: currentCount * 2 });
}
}));
function Counter() {
const { count, increment, decrement, incrementBy, reset, double } = useCounterStore();
return (
<div>
<h2>Count: {count}</h2>
<button onClick={increment}>+1</button>
<button onClick={decrement}>-1</button>
<button onClick={() => incrementBy(5)}>+5</button>
<button onClick={double}>×2</button>
<button onClick={reset}>Reset</button>
</div>
);
}示例2:Todo应用
jsx
import { create } from 'zustand';
const useTodoStore = create((set) => ({
todos: [],
filter: 'all',
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)
})),
setFilter: (filter) => set({ filter }),
clearCompleted: () =>
set((state) => ({
todos: state.todos.filter((todo) => !todo.completed)
}))
}));
// 计算属性选择器
const selectFilteredTodos = (state) => {
const { todos, filter } = state;
switch (filter) {
case 'active':
return todos.filter((todo) => !todo.completed);
case 'completed':
return todos.filter((todo) => todo.completed);
default:
return todos;
}
};
function TodoApp() {
const filteredTodos = useTodoStore(selectFilteredTodos);
const { addTodo, toggleTodo, deleteTodo, setFilter, clearCompleted } = useTodoStore();
const [input, setInput] = useState('');
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 todo..."
/>
<button type="submit">Add</button>
</form>
<div>
<button onClick={() => setFilter('all')}>All</button>
<button onClick={() => setFilter('active')}>Active</button>
<button onClick={() => setFilter('completed')}>Completed</button>
</div>
<ul>
{filteredTodos.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>
<button onClick={clearCompleted}>Clear Completed</button>
</div>
);
}示例3:用户认证
jsx
import { create } from 'zustand';
const useAuthStore = create((set) => ({
user: null,
token: null,
loading: false,
error: null,
login: async (credentials) => {
set({ loading: true, error: null });
try {
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(credentials)
});
if (!response.ok) {
throw new Error('Login failed');
}
const { user, token } = await response.json();
set({ user, token, loading: false });
// 保存token
localStorage.setItem('token', token);
} catch (error) {
set({ error: error.message, loading: false });
}
},
logout: () => {
set({ user: null, token: null });
localStorage.removeItem('token');
},
checkAuth: async () => {
const token = localStorage.getItem('token');
if (!token) return;
set({ loading: true });
try {
const response = await fetch('/api/me', {
headers: { Authorization: `Bearer ${token}` }
});
if (response.ok) {
const user = await response.json();
set({ user, token, loading: false });
} else {
set({ user: null, token: null, loading: false });
localStorage.removeItem('token');
}
} catch (error) {
set({ error: error.message, loading: false });
}
}
}));
// 使用
function LoginForm() {
const { login, loading, error } = useAuthStore();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
await login({ email, password });
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
/>
<button type="submit" disabled={loading}>
{loading ? 'Logging in...' : 'Login'}
</button>
{error && <p className="error">{error}</p>}
</form>
);
}
function UserProfile() {
const user = useAuthStore((state) => state.user);
const logout = useAuthStore((state) => state.logout);
if (!user) {
return <LoginForm />;
}
return (
<div>
<h2>Welcome, {user.name}</h2>
<p>Email: {user.email}</p>
<button onClick={logout}>Logout</button>
</div>
);
}高级特性
1. 不可变更新模式
jsx
import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer';
// 使用immer中间件
const useStore = create(
immer((set) => ({
nested: {
level1: {
level2: {
value: 0
}
}
},
// 直接修改,immer处理不可变性
updateNested: (newValue) =>
set((state) => {
state.nested.level1.level2.value = newValue;
}),
// 数组操作
items: [],
addItem: (item) =>
set((state) => {
state.items.push(item);
}),
removeItem: (index) =>
set((state) => {
state.items.splice(index, 1);
})
}))
);2. 派生状态
jsx
const useStore = create((set, get) => ({
items: [],
filter: '',
// Actions
setItems: (items) => set({ items }),
setFilter: (filter) => set({ filter }),
// Getters(派生状态)
get filteredItems() {
return get().items.filter((item) =>
item.name.toLowerCase().includes(get().filter.toLowerCase())
);
},
get itemCount() {
return get().items.length;
},
get filteredCount() {
return this.filteredItems.length;
}
}));
// 使用
function ItemList() {
// 使用选择器获取派生状态
const filteredItems = useStore((state) =>
state.items.filter((item) =>
item.name.toLowerCase().includes(state.filter.toLowerCase())
)
);
return (
<ul>
{filteredItems.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}3. 临时更新(Transient Updates)
jsx
const useStore = create((set) => ({
x: 0,
y: 0,
// 不触发订阅的更新
setCoordinates: (x, y) => {
useStore.setState({ x, y }, true); // replace参数为true
}
}));
// 在React外部使用
useStore.getState().setCoordinates(100, 200);4. 订阅部分状态
jsx
const useStore = create((set) => ({
count: 0,
user: { name: 'Alice' },
settings: { theme: 'light' }
}));
function Component() {
// 只订阅count
const count = useStore((state) => state.count);
// 使用浅比较订阅多个值
const { user, settings } = useStore(
(state) => ({ user: state.user, settings: state.settings }),
shallow
);
return <div>{count}</div>;
}React外部使用
在vanilla JS中使用
jsx
import { create } from 'zustand/vanilla';
// 创建vanilla store
const store = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 }))
}));
// 获取状态
const state = store.getState();
console.log(state.count); // 0
// 更新状态
store.getState().increment();
console.log(store.getState().count); // 1
// 订阅变化
const unsubscribe = store.subscribe((state) => {
console.log('State changed:', state);
});
// 取消订阅
unsubscribe();在React中使用vanilla store
jsx
import { create } from 'zustand/vanilla';
import { useStore } from 'zustand';
// 创建vanilla store
const vanillaStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 }))
}));
// 在React组件中使用
function Counter() {
const count = useStore(vanillaStore, (state) => state.count);
const increment = useStore(vanillaStore, (state) => state.increment);
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
}TypeScript支持
基础类型定义
typescript
import { create } from 'zustand';
interface CounterState {
count: number;
increment: () => void;
decrement: () => void;
reset: () => void;
}
const useCounterStore = create<CounterState>((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
reset: () => set({ count: 0 })
}));复杂类型定义
typescript
import { create } from 'zustand';
interface User {
id: number;
name: string;
email: string;
}
interface AuthState {
user: User | null;
token: string | null;
loading: boolean;
error: string | null;
login: (credentials: { email: string; password: string }) => Promise<void>;
logout: () => void;
checkAuth: () => Promise<void>;
}
const useAuthStore = create<AuthState>((set) => ({
user: null,
token: null,
loading: false,
error: null,
login: async (credentials) => {
set({ loading: true, error: null });
try {
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(credentials)
});
const { user, token } = await response.json();
set({ user, token, loading: false });
} catch (error) {
set({ error: (error as Error).message, loading: false });
}
},
logout: () => set({ user: null, token: null }),
checkAuth: async () => {
// 实现...
}
}));使用State Creator类型
typescript
import { create, StateCreator } from 'zustand';
type CounterSlice = {
count: number;
increment: () => void;
};
type UserSlice = {
user: User | null;
setUser: (user: User) => void;
};
const createCounterSlice: StateCreator<CounterSlice> = (set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 }))
});
const createUserSlice: StateCreator<UserSlice> = (set) => ({
user: null,
setUser: (user) => set({ user })
});
// 组合slices
const useStore = create<CounterSlice & UserSlice>()((...a) => ({
...createCounterSlice(...a),
...createUserSlice(...a)
}));性能优化
1. 使用选择器
jsx
// 不好:选择整个store
const Component = () => {
const store = useStore();
return <div>{store.count}</div>;
};
// 好:只选择需要的部分
const Component = () => {
const count = useStore((state) => state.count);
return <div>{count}</div>;
};2. 使用浅比较
jsx
import { shallow } from 'zustand/shallow';
const Component = () => {
// 使用浅比较避免不必要的重渲染
const { count, user } = useStore(
(state) => ({ count: state.count, user: state.user }),
shallow
);
return <div>{count} - {user.name}</div>;
};3. 分离数据和操作
jsx
const Component = () => {
// 数据会触发重渲染
const count = useStore((state) => state.count);
// 操作不会触发重渲染(引用稳定)
const increment = useStore((state) => state.increment);
return (
<div>
<p>{count}</p>
<button onClick={increment}>Increment</button>
</div>
);
};调试技巧
使用Redux DevTools
jsx
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
const useStore = create(
devtools(
(set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 }))
}),
{ name: 'CounterStore' }
)
);日志中间件
jsx
const log = (config) => (set, get, api) =>
config(
(...args) => {
console.log('Previous state:', get());
set(...args);
console.log('New state:', get());
},
get,
api
);
const useStore = create(
log((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 }))
}))
);最佳实践
1. Store组织
jsx
// 按功能域划分store
const useAuthStore = create((set) => ({...}));
const useUserStore = create((set) => ({...}));
const useProductStore = create((set) => ({...}));
// 或使用slices模式
const useStore = create((set) => ({
// Auth slice
user: null,
login: () => {...},
logout: () => {...},
// Products slice
products: [],
fetchProducts: () => {...},
// Cart slice
cart: [],
addToCart: () => {...}
}));2. 异步操作
jsx
const useStore = create((set) => ({
data: null,
loading: false,
error: null,
fetchData: async () => {
set({ loading: true, error: null });
try {
const response = await fetch('/api/data');
const data = await response.json();
set({ data, loading: false });
} catch (error) {
set({ error: error.message, loading: false });
}
}
}));3. 命名约定
jsx
// Store名称:use + 领域 + Store
const useUserStore = create((set) => ({...}));
const useProductStore = create((set) => ({...}));
// Action名称:动词开头
const useStore = create((set) => ({
// 好
fetchUsers: () => {...},
updateUser: () => {...},
deleteUser: () => {...},
// 不好
users: () => {...},
user: () => {...}
}));总结
Zustand是一个简单、灵活、高性能的状态管理库。关键要点:
- 简单API:create创建store,使用Hook订阅
- 无样板代码:不需要Provider和action types
- 灵活选择器:精确订阅需要的状态
- TypeScript支持:完善的类型定义
- 中间件支持:devtools、persist、immer等
- React外使用:vanilla版本支持非React环境
Zustand非常适合中小型应用的状态管理,也可以通过slices模式扩展到大型应用。