Zustand 的核心优势是 API 极简、零模板代码、无需 Provider ,只需掌握 create(创建 Store)和 useStore(组件订阅)两个核心 API,就能快速实现全局/局部状态共享。下面从「安装 → 基础使用 → 核心特性 → 实战示例」一步步带你入门:
一、环境准备与安装
1. 安装依赖
支持 React(≥16.8,需 Hooks 支持)、Vue(通过适配器)、原生 JS,这里以 React + TS 为例(JS 项目可忽略类型定义):
bash
# npm
npm install zustand --save
# yarn
yarn add zustand
# pnpm
pnpm add zustand
2. 兼容性
- React:≥16.8(支持 Hooks)
- TypeScript:≥4.1(自动类型推导,无需额外配置)
- 浏览器:支持 ES6+ 的现代浏览器(IE 不兼容)
二、核心概念速览
Zustand 没有复杂概念,核心只有 2 个:
- Store:状态容器,存储全局共享的状态和修改状态的方法(类似「数据 + 方法」的集合)。
- useStore:React Hooks,用于在组件中订阅 Store 的状态、调用 Store 的方法。
无需 Provider 包裹应用、无需 Action Type、无需 Reducer,直接定义、直接使用!
三、基础使用:3 步实现全局状态
以「全局计数器」为例,快速体验 Zustand 的简洁:
1. 第一步:创建 Store
新建 stores/counterStore.ts(按模块拆分 Store,模块化更清晰):
typescript
import { create } from 'zustand';
// 创建 Store:参数是一个函数,返回「状态 + 修改方法」
const useCounterStore = create((set) => ({
// 1. 状态(类似 React 的 useState)
count: 0,
// 2. 修改状态的方法(直接定义,无需 Action/Reducer)
// set:用于修改状态的工具函数,接收新状态或状态更新函数
increment: () => set((state) => ({ count: state.count + 1 })), // 函数式更新(依赖旧状态)
decrement: () => set((state) => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }), // 直接设置新状态(不依赖旧状态)
setCount: (newCount: number) => set({ count: newCount }), // 接收参数修改状态
}));
export default useCounterStore;
关键说明:
create函数返回一个自定义 Hooks(如useCounterStore),组件通过这个 Hooks 访问状态。set函数:修改状态时必须通过set(保证状态更新的可追踪性),支持两种写法:- 函数式:
set(state => ({ ... }))(依赖旧状态,如计数递增) - 对象式:
set({ key: value })(不依赖旧状态,如重置)
- 函数式:
2. 第二步:组件中使用 Store
在任意组件中直接导入 useCounterStore,无需其他配置:
tsx
// CounterComponent.tsx
import useCounterStore from '@/stores/counterStore';
const CounterComponent = () => {
// 方式 1:订阅单个状态(推荐,性能更优:仅 count 变更时组件重渲染)
const count = useCounterStore((state) => state.count);
// 方式 2:订阅多个状态/方法(解构,注意:状态变更会导致组件重渲染)
const { increment, decrement, reset, setCount } = useCounterStore();
return (
<div>
<h1>计数:{count}</h1>
<button onClick={increment}>+1</button>
<button onClick={decrement}>-1</button>
<button onClick={reset}>重置</button>
<button onClick={() => setCount(100)}>设置为 100</button>
</div>
);
};
export default CounterComponent;
性能优化技巧:
- 订阅单个状态(
useCounterStore(state => state.count)):组件仅在该状态变更时重渲染,比解构整个 Store 性能更优。 - 若需订阅多个状态,可使用
shallow比较(下文「核心特性」会讲)。
3. 第三步:多组件共享状态
再创建一个组件,同样导入 useCounterStore,即可共享同一状态:
tsx
// CounterDisplay.tsx
import useCounterStore from '@/stores/counterStore';
const CounterDisplay = () => {
// 订阅 count 状态
const count = useCounterStore((state) => state.count);
return <div>全局计数(另一个组件):{count}</div>;
};
export default CounterDisplay;
此时,点击 CounterComponent 的按钮,CounterDisplay 的计数会自动同步更新------全局状态共享就这么简单!
四、核心特性:解决实际开发场景
1. 订阅多个状态(避免冗余渲染)
直接解构多个状态会导致「任意状态变更都触发组件重渲染」,可使用 shallow 比较优化:
typescript
import { create } from 'zustand';
import { shallow } from 'zustand/shallow'; // 导入浅比较工具
const useUserStore = create((set) => ({
name: '张三',
age: 20,
setName: (name: string) => set({ name }),
setAge: (age: number) => set({ age }),
}));
// 组件中使用:仅当 name 或 age 实际变更时,组件才重渲染
const UserComponent = () => {
const { name, age } = useUserStore(
(state) => ({ name: state.name, age: state.age }),
shallow // 第二个参数:浅比较(对比对象的第一层属性)
);
return <div>姓名:{name},年龄:{age}</div>;
};
2. 异步状态处理(如接口请求)
Zustand 支持直接在方法中写异步逻辑,无需额外中间件(如 Redux 的 Thunk):
typescript
// stores/userStore.ts
import { create } from 'zustand';
import axios from 'axios';
const useUserStore = create((set) => ({
userInfo: null,
loading: false,
error: null,
// 异步获取用户信息
fetchUser: async (userId: number) => {
set({ loading: true, error: null }); // 开始请求:设置加载中
try {
const res = await axios.get(`/api/user/${userId}`);
set({ userInfo: res.data, loading: false }); // 请求成功:更新状态
} catch (err) {
set({ error: err.message, loading: false }); // 请求失败:捕获错误
}
},
}));
// 组件中使用
const UserProfile = () => {
const { userInfo, loading, error, fetchUser } = useUserStore();
useEffect(() => {
fetchUser(1); // 组件挂载时请求用户信息
}, [fetchUser]);
if (loading) return <div>加载中...</div>;
if (error) return <div>错误:{error}</div>;
if (!userInfo) return null;
return <div>姓名:{userInfo.name},邮箱:{userInfo.email}</div>;
};
3. 多 Store 设计(模块化)
Zustand 推荐按业务模块拆分多个独立 Store(如用户、购物车、全局配置),避免单个 Store 过于庞大:
stores/
├── userStore.ts // 用户相关状态
├── cartStore.ts // 购物车相关状态
└── configStore.ts // 全局配置(如主题、语言)
示例:购物车 Store(cartStore.ts):
typescript
import { create } from 'zustand';
const useCartStore = create((set) => ({
cartItems: [], // 购物车商品列表:[{ id, name, price, quantity }]
// 添加商品到购物车
addToCart: (item) => set((state) => ({
cartItems: [...state.cartItems, { ...item, quantity: 1 }],
})),
// 更新商品数量
updateQuantity: (id, quantity) => set((state) => ({
cartItems: state.cartItems.map((item) =>
item.id === id ? { ...item, quantity } : item
),
})),
// 清空购物车
clearCart: () => set({ cartItems: [] }),
}));
export default useCartStore;
4. 状态持久化(本地存储)
使用 zustand-persist 插件,轻松实现状态持久化(刷新页面不丢失):
安装插件:
bash
npm install zustand-persist --save
实现持久化:
typescript
import { create } from 'zustand';
import { persist } from 'zustand-persist';
const useThemeStore = create(
persist(
(set) => ({
theme: 'light', // 主题:light/dark
toggleTheme: () => set((state) => ({
theme: state.theme === 'light' ? 'dark' : 'light',
})),
}),
{
name: 'theme-storage', // 本地存储的 key(localStorage 中显示为 `zustand-theme-storage`)
getStorage: () => localStorage, // 存储方式:localStorage(默认)或 sessionStorage
}
)
);
// 组件中使用:刷新页面后,theme 状态会从 localStorage 恢复
const ThemeToggle = () => {
const { theme, toggleTheme } = useThemeStore();
return <button onClick={toggleTheme}>切换{theme === 'light' ? '深色' : '浅色'}主题</button>;
};
5. 调试:集成 Redux DevTools
Zustand 支持接入 Redux DevTools,实现状态回溯、Action 日志等调试功能:
typescript
import { create } from 'zustand';
import { devtools } from 'zustand/middleware'; // 导入调试中间件
// 创建 Store 时包裹 devtools 中间件
const useCounterStore = create(
devtools((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}), {
name: 'counter-store', // DevTools 中显示的 Store 名称
})
);
启用调试:
- 安装 Chrome 插件「Redux DevTools」。
- 打开浏览器开发者工具 → Redux 面板,即可看到
counter-store的状态变更日志,支持时间旅行调试。
五、实战示例:电商购物车
整合上述特性,实现一个完整的电商购物车 Store:
typescript
// stores/cartStore.ts
import { create } from 'zustand';
import { persist } from 'zustand-persist';
import { devtools } from 'zustand/middleware';
import { shallow } from 'zustand/shallow';
// 商品类型定义(TS)
interface CartItem {
id: number;
name: string;
price: number;
quantity: number;
img: string;
}
// 创建 Store:结合持久化 + 调试中间件
const useCartStore = create(
devtools(
persist(
(set, get) => ({
// 状态
cartItems: [] as CartItem[],
// 计算属性:购物车商品总数
getTotalCount: () => {
return get().cartItems.reduce((total, item) => total + item.quantity, 0);
},
// 计算属性:购物车总价
getTotalPrice: () => {
return get().cartItems.reduce((total, item) => total + item.price * item.quantity, 0);
},
// 方法:添加商品
addToCart: (item: Omit<CartItem, 'quantity'>) => {
const existingItem = get().cartItems.find((i) => i.id === item.id);
if (existingItem) {
// 商品已存在:更新数量
set((state) => ({
cartItems: state.cartItems.map((i) =>
i.id === item.id ? { ...i, quantity: i.quantity + 1 } : i
),
}), false, 'cart/add'); // 第三个参数:Action 名称(调试用)
} else {
// 商品不存在:添加新商品
set((state) => ({
cartItems: [...state.cartItems, { ...item, quantity: 1 }],
}), false, 'cart/add');
}
},
// 方法:更新数量
updateQuantity: (id: number, quantity: number) => {
set((state) => ({
cartItems: state.cartItems.map((i) =>
i.id === id ? { ...i, quantity: Math.max(1, quantity) } : i // 数量至少为 1
),
}), false, 'cart/update');
},
// 方法:删除商品
removeItem: (id: number) => {
set((state) => ({
cartItems: state.cartItems.filter((i) => i.id !== id),
}), false, 'cart/remove');
},
// 方法:清空购物车
clearCart: () => {
set({ cartItems: [] }, false, 'cart/clear');
},
}),
{
name: 'cart-storage', // 本地存储 key
getStorage: () => localStorage,
}
)
)
);
// 导出简化的 Hooks(可选,方便组件使用)
export const useCartItems = () => useCartStore((state) => state.cartItems);
export const useCartActions = () =>
useCartStore(
(state) => ({
addToCart: state.addToCart,
updateQuantity: state.updateQuantity,
removeItem: state.removeItem,
clearCart: state.clearCart,
}),
shallow
);
export const useCartSummary = () =>
useCartStore(
(state) => ({
totalCount: state.getTotalCount(),
totalPrice: state.getTotalPrice(),
}),
shallow
);
export default useCartStore;
组件中使用:
tsx
// CartPage.tsx
import { useCartItems, useCartActions, useCartSummary } from '@/stores/cartStore';
const CartPage = () => {
const cartItems = useCartItems();
const { addToCart, updateQuantity, removeItem, clearCart } = useCartActions();
const { totalCount, totalPrice } = useCartSummary();
// 示例:添加商品
const handleAddTestItem = () => {
addToCart({
id: Date.now(),
name: '测试商品',
price: 99,
img: 'https://picsum.photos/200',
});
};
if (cartItems.length === 0) {
return (
<div>
<h1>购物车为空</h1>
<button onClick={handleAddTestItem}>添加测试商品</button>
</div>
);
}
return (
<div>
<h1>购物车({totalCount} 件商品)</h1>
<button onClick={clearCart}>清空购物车</button>
<ul>
{cartItems.map((item) => (
<li key={item.id}>
<img src={item.img} alt={item.name} style={{ width: '50px' }} />
<div>{item.name}</div>
<div>¥{item.price}</div>
<button onClick={() => updateQuantity(item.id, item.quantity - 1)}>-</button>
<span>{item.quantity}</span>
<button onClick={() => updateQuantity(item.id, item.quantity + 1)}>+</button>
<button onClick={() => removeItem(item.id)}>删除</button>
</li>
))}
</ul>
<div>总价:¥{totalPrice.toFixed(2)}</div>
</div>
);
};
export default CartPage;
六、常见问题与注意事项
1. 避免闭包陷阱
组件中直接缓存 Store 方法可能导致闭包陷阱(获取到旧状态),解决方案:
-
不缓存方法,直接通过
useCartStore获取(推荐)。 -
若需缓存,使用
useCallback并依赖 Store 方法:tsxconst { addToCart } = useCartActions(); const handleAdd = useCallback(() => { addToCart({ id: 1, name: '商品', price: 100, img: '' }); }, [addToCart]); // 依赖 addToCart(确保方法是最新的)
2. 状态不可变性(可选)
Zustand 支持 mutable 模式(直接修改状态),但推荐保持不可变性(便于调试和时间旅行):
typescript
// 不推荐(mutable,虽然能工作,但调试不便)
const useCounterStore = create((set) => ({
count: 0,
increment: () => set((state) => {
state.count += 1; // 直接修改状态(mutable)
return state;
}),
}));
// 推荐(immutable,返回新状态)
const useCounterStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}));
3. 组件卸载时取消订阅(无需手动处理)
Zustand 的 useStore 会自动在组件卸载时取消订阅,无需手动清理,避免内存泄漏。
七、总结
Zustand 入门门槛极低,核心 API 仅 2 个,却能覆盖绝大多数前端状态管理场景:
- 简单场景:直接定义状态和方法,组件中订阅使用。
- 复杂场景:通过中间件(持久化、调试)、浅比较、异步方法扩展。
核心优势:极简 API、零模板代码、高性能、TS 友好、无需 Provider。
如果你的项目是 React 技术栈,且需要一个「轻量、高效、易上手」的状态管理库,Zustand 绝对是首选!