1361 words
7 minutes
React zustand
一、为什么需要 Zustand?从 React 状态管理的困境说起
在 React 中,我们常用的状态管理方式主要有两种,但它们在复杂应用中都存在明显局限:
- 组件内状态(useState):状态仅局限于当前组件,若其他组件需要使用,必须通过 props 层层传递。在深层嵌套的组件结构中,这种方式会导致代码冗余、维护困难,也就是常说的 “props 透传地狱”。
- Context API:作为 React 原生的跨组件状态方案,它需要用 Provider 包裹应用或组件树的一部分,这可能引发不必要的重渲染,尤其在大型应用中,性能问题会逐渐凸显。
而 Zustand 的出现,正是为了解决这些问题。它是一款轻量级状态管理库,核心优势在于:
- 无需用 Provider 包裹应用,简化代码结构;
- 支持全局状态管理,任意组件可直接访问和修改状态;
- 语法简洁,学习成本低,无需冗余的样板代码。
二、Zustand 基础用法:从一个计数器案例开始
- 创建 Store:状态的 “容器”
Zustand 中,状态和状态更新函数都存放在 “store” 中。创建 store 只需两步:定义类型(TypeScript)和初始化状态。
// src/store/counterStore.ts
import { create } from 'zustand';
// 定义 store 类型
type CounterStore = {
count: number;
increment: () => void;
decrement: () => void;
};
// 创建 store(本质是一个自定义 Hook)
export const useCounterStore = create<CounterStore>((set) => ({
count: 0, // 初始状态
// 同步更新函数:使用 set 函数修改状态
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}));
- create 是 Zustand 的核心函数,用于创建 store;
- set 函数用于更新状态,它可以接收一个函数(参数为当前状态),返回新的状态对象;
- 命名规范:store 以 use 开头,符合 React Hook 的命名约定,便于在组件中直接使用。
- 在组件中使用状态:简单到像用 Hook
Zustand 的 store 本质是一个自定义 Hook,因此在组件中使用时,就像调用普通 Hook 一样简单。
// src/components/CounterDisplay.tsx
import { useCounterStore } from '../store/counterStore';
const CounterDisplay = () => {
// 仅获取需要的状态(精准选择,提升性能)
const count = useCounterStore((state) => state.count);
return <h2>当前计数:{count}</h2>;
};
export default CounterDisplay;
// src/components/CounterControls.tsx
import { useCounterStore } from '../store/counterStore';
const CounterControls = () => {
// 获取状态更新函数
const increment = useCounterStore((state) => state.increment);
const decrement = useCounterStore((state) => state.decrement);
return (
<div>
<button onClick={increment}>+1</button>
<button onClick={decrement}>-1</button>
</div>
);
};
export default CounterControls;
- 无需传递 props,两个组件通过同一个 store 实现状态同步;
- 即使组件不在同一层级,也能轻松共享状态,彻底解决 “props 透传” 问题。
三、异步状态更新:无需中间件,直接上手
在实际开发中,我们经常需要处理异步操作(如 API 请求),然后根据结果更新状态。Zustand 对异步操作的支持非常友好,无需额外配置中间件。
// 扩展 counterStore,添加异步更新函数
type CounterStore = {
// ... 原有状态和函数
incrementAsync: () => Promise<void>; // 异步函数类型
};
export const useCounterStore = create<CounterStore>((set) => ({
// ... 原有状态和函数
// 异步更新:模拟 API 请求延迟
incrementAsync: async () => {
// 模拟 1 秒后获取数据
await new Promise((resolve) => setTimeout(resolve, 1000));
// 延迟后更新状态
set((state) => ({ count: state.count + 1 }));
},
}));
在组件中使用异步函数,与同步函数完全一致:
// 在 CounterControls 中添加异步按钮
const incrementAsync = useCounterStore((state) => state.incrementAsync);
// 按钮绑定
<button onClick={incrementAsync}>异步 +1(延迟 1 秒)</button>
四、非组件环境操作状态:灵活度拉满
Zustand 不仅能在组件中使用,还支持在普通函数(如工具函数、API 回调)中直接操作状态,这在处理复杂业务逻辑时非常实用。
- 获取状态
// src/utils/logCount.ts
import { useCounterStore } from '../store/counterStore';
// 非组件函数中获取状态
export const logCount = () => {
const currentCount = useCounterStore.getState().count;
console.log('当前计数:', currentCount);
};
在组件中调用这些函数,状态会自动同步并触发重渲染:
import { useEffect } from 'react';
import { logCount, initCount } from '../utils/countUtils';
const CounterInitializer = () => {
useEffect(() => {
logCount(); // 打印初始值
}, []);
return null;
};
五、Zustand 最佳实践:让你的代码更高效
- 精准选择状态,避免不必要的重渲染 访问状态时,尽量只获取需要的字段,而非整个 state 对象。例如:
// 推荐:仅监听 count 变化
const count = useCounterStore((state) => state.count);
// 不推荐:监听整个 state,任何字段变化都会触发重渲染
const { count } = useCounterStore((state) => state);
- 按功能拆分 store,提升模块化 不要把所有状态都塞进一个 store,应根据业务功能拆分(如计数器 store、用户认证 store、购物车 store 等)。这样既便于维护,也能减少状态更新对无关组件的影响。
六、总结:为什么选择 Zustand?
Zustand 凭借其简洁的 API、灵活的使用方式和优秀的性能,成为 React 状态管理的优选方案之一。它的核心优势包括:
- 零配置:无需 Provider 包裹,开箱即用;
- 轻量高效:代码量少,性能优异,避免不必要的重渲染;
- 支持同步 / 异步操作:轻松处理各种状态更新场景;
- 跨环境兼容:既能在组件中使用,也能在普通函数中操作状态。
React zustand
https://fuwari.vercel.app/posts/front/react/zustand/