Zustand

Zustand 状态管理工具用法详解

Zustand 是一款轻量级、无 Provider 包裹、API 简洁的状态管理工具,核心优势是上手快、代码量少、不依赖 React 上下文,适合中小型项目或大型项目中的独立模块状态管理。

一、核心特点

  1. 无 Provider 嵌套:不需要像 Redux/Vuex 那样用 Provider 包裹整个应用,直接创建 Store 即可使用;
  2. 简洁 API :通过 create 函数创建 Store,useStore 钩子订阅状态,学习成本极低;
  3. 支持中间件:内置持久化、日志、撤销/重做等中间件,扩展能力强;
  4. 轻量化:体积仅 1KB 左右,无多余依赖;
  5. React 友好:天然支持 React hooks,也可用于 Vue/原生 JS 项目。

二、安装

bash 复制代码
# npm
npm install zustand

# yarn
yarn add zustand

# pnpm
pnpm add zustand

三、基础用法(React 场景)

1. 创建 Store(核心步骤)

Store 是状态的容器,包含「状态数据」和「修改状态的方法」,通常单独创建文件(如 src/store/index.js):

javascript 复制代码
import { create } from 'zustand';

// 创建 Store:参数是一个函数,返回 { 状态, 状态修改方法 }
const useCounterStore = create((set) => ({
  // 1. 状态数据
  count: 0,
  name: 'Zustand',
  
  // 2. 修改状态的方法(通过 set 函数更新状态)
  // set 接收:(prevState) => newState,支持函数式更新(确保拿到最新状态)
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
  
  // 带参数的修改方法
  setCount: (newCount) => set({ count: newCount }), // 直接传对象(无需依赖旧状态时)
  
  // 批量修改多个状态
  reset: () => set({ count: 0, name: 'Zustand Reset' }),
}));

export default useCounterStore;
  • create 函数:接收一个「状态初始化函数」,返回一个「自定义 hooks」(如 useCounterStore);
  • set 函数:用于修改状态,支持两种写法:
    • 函数式:set(prev => ({ count: prev.count + 1 }))(依赖旧状态时必须用);
    • 对象式:set({ count: 10 })(不依赖旧状态时简化使用)。

2. 组件中使用 Store

通过自定义 hooks useCounterStore 订阅状态和调用方法,支持「订阅整个 Store」或「订阅部分状态」(性能更优):

jsx 复制代码
import useCounterStore from '@/store';

function Counter() {
  // 方式1:订阅单个状态(推荐,减少不必要的重渲染)
  const count = useCounterStore((state) => state.count);
  const name = useCounterStore((state) => state.name);
  
  // 方式2:订阅多个状态(通过 shallow 浅比较,避免因对象引用变化导致重渲染)
  const { increment, decrement } = useCounterStore(
    (state) => ({
      increment: state.increment,
      decrement: state.decrement,
    }),
    { equals: shallow } // 需要导入:import { shallow } from 'zustand/shallow'
  );
  
  // 方式3:订阅整个 Store(不推荐,状态变化时组件会全量重渲染)
  // const store = useCounterStore();

  return (
    <div>
      <h1>{name}</h1>
      <p>Count: {count}</p>
      <button onClick={increment}>+1</button>
      <button onClick={decrement}>-1</button>
      <button onClick={() => useCounterStore.getState().setCount(100)}>
        直接修改(非组件环境可用)
      </button>
    </div>
  );
}
  • 关键 API:
    • useCounterStore(selector):订阅状态,selector 函数返回需要的状态/方法;
    • useCounterStore.getState():非组件环境(如工具函数、定时器)获取当前状态;
    • useCounterStore.setState(newState):非组件环境修改状态(如 useCounterStore.setState({ count: 10 }));
    • shallow 浅比较:当订阅多个状态时,用 shallow 避免因对象引用变化导致的不必要重渲染(默认是全等比较)。

3. 异步状态管理(常见场景)

Zustand 天然支持异步方法,直接在 Store 中定义异步函数即可(无需额外中间件):

javascript 复制代码
import { create } from 'zustand';
import axios from 'axios';

// 示例:用户信息 Store
const useUserStore = create((set) => ({
  userInfo: null,
  loading: false,
  error: null,
  
  // 异步获取用户信息
  fetchUser: async (userId) => {
    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 }); // 请求失败:更新错误信息
    }
  },
  
  // 退出登录:重置状态
  logout: () => set({ userInfo: null }),
}));

export default useUserStore;

组件中使用:

jsx 复制代码
function UserProfile() {
  const { userInfo, loading, error, fetchUser, logout } = useUserStore(
    (state) => ({
      userInfo: state.userInfo,
      loading: state.loading,
      error: state.error,
      fetchUser: state.fetchUser,
      logout: state.logout,
    }),
    { equals: shallow }
  );

  useEffect(() => {
    fetchUser(1); // 组件挂载时获取用户信息
  }, [fetchUser]);

  if (loading) return <div>加载中...</div>;
  if (error) return <div>错误:{error}</div>;
  if (!userInfo) return <div>未登录</div>;

  return (
    <div>
      <h1>{userInfo.name}</h1>
      <button onClick={logout}>退出登录</button>
    </div>
  );
}

四、进阶用法

1. 持久化存储(localStorage/sessionStorage)

通过 zustand/persist 中间件实现状态持久化,刷新页面后状态不丢失:

javascript 复制代码
import { create } from 'zustand';
import { persist } from 'zustand/middleware';

const useAuthStore = create(
  persist(
    (set) => ({
      token: null,
      login: (token) => set({ token }),
      logout: () => set({ token: null }),
    }),
    {
      name: 'auth-storage', // 存储在 localStorage 中的 key(默认是 'zustand')
      getStorage: () => localStorage, // 可选:指定存储方式(localStorage/sessionStorage)
      // 可选:自定义序列化/反序列化(如加密敏感数据)
      // serialize: (state) => JSON.stringify({ ...state, token: encrypt(token) }),
      // deserialize: (str) => { const state = JSON.parse(str); return { ...state, token: decrypt(token) }; }
    }
  )
);

export default useAuthStore;
  • 原理:将状态自动同步到 localStorage,页面刷新时从 localStorage 恢复状态;
  • 注意:持久化的状态需是可序列化的(不能包含函数、Symbol 等)。

2. 拆分多个 Store(模块化)

大型项目中可按功能拆分多个 Store(如用户、购物车、设置),互不干扰:

javascript 复制代码
// src/store/userStore.js
import { create } from 'zustand';
export const useUserStore = create((set) => ({
  userInfo: null,
  setUserInfo: (info) => set({ userInfo: info }),
}));

// src/store/cartStore.js
import { create } from 'zustand';
export const useCartStore = create((set) => ({
  cartList: [],
  addToCart: (goods) => set((state) => ({ cartList: [...state.cartList, goods] })),
}));

组件中按需导入:

jsx 复制代码
import { useUserStore } from '@/store/userStore';
import { useCartStore } from '@/store/cartStore';

function App() {
  const userInfo = useUserStore((state) => state.userInfo);
  const addToCart = useCartStore((state) => state.addToCart);
  // ...
}

3. 中间件:日志、撤销/重做

Zustand 支持自定义中间件,内置常用中间件:

(1)日志中间件(log

打印状态变化日志(开发环境用):

javascript 复制代码
import { create } from 'zustand';
import { log } from 'zustand/middleware';

const useCounterStore = create(
  log((set) => ({
    count: 0,
    increment: () => set((state) => ({ count: state.count + 1 })),
  }))
);
  • 效果:每次状态变化时,控制台会打印「旧状态→新状态→触发的动作」。
(2)撤销/重做中间件(undo

支持状态的撤销(undo)和重做(redo):

javascript 复制代码
import { create } from 'zustand';
import { undo } from 'zustand/middleware';

const useEditStore = create(
  undo((set) => ({
    content: '',
    setContent: (text) => set({ content: text }),
  }))
);

// 组件中使用
function Editor() {
  const { content, setContent, undo, redo, canUndo, canRedo } = useEditStore();
  return (
    <div>
      <textarea value={content} onChange={(e) => setContent(e.target.value)} />
      <button onClick={undo} disabled={!canUndo}>撤销</button>
      <button onClick={redo} disabled={!canRedo}>重做</button>
    </div>
  );
}

4. 状态派生(计算属性)

通过 selector 函数实现派生状态(类似 Vuex 的 getters):

javascript 复制代码
import { create } from 'zustand';

const useCartStore = create((set) => ({
  cartList: [
    { id: 1, name: '手机', price: 5000, quantity: 2 },
    { id: 2, name: '耳机', price: 1000, quantity: 1 },
  ],
  addToCart: (goods) => set((state) => ({ cartList: [...state.cartList, goods] })),
}));

// 组件中派生「购物车总价」
function Cart() {
  // 派生状态:计算总价(依赖 cartList,cartList 变化时自动更新)
  const totalPrice = useCartStore((state) => 
    state.cartList.reduce((sum, item) => sum + item.price * item.quantity, 0)
  );

  return <div>总价:{totalPrice} 元</div>;
}

五、与 Redux 的对比(为什么选 Zustand?)

特性 Zustand Redux
代码量 极少(无 Provider、Action Type) 较多(需定义 Action、Reducer、Store)
上手难度 低(API 简洁) 高(需理解 Redux 三大原则)
状态订阅 精确订阅(按需更新) 需配合 reselect 优化订阅
中间件 内置支持(持久化、日志等) 需手动集成(redux-thunk、redux-saga)
适用场景 中小型项目、独立模块 大型复杂项目(需严格状态管控)

六、最佳实践

  1. 按需订阅状态 :尽量用 selector 订阅单个/部分状态,避免订阅整个 Store(减少重渲染);
  2. 多个状态订阅用 shallow :订阅多个状态时,添加 { equals: shallow } 浅比较;
  3. 拆分模块化 Store:按功能拆分 Store(如用户、购物车、设置),避免单个 Store 过大;
  4. 敏感数据加密 :持久化存储时(如 token),对敏感数据进行加密(自定义 serialize 方法);
  5. 非组件环境使用 :通过 useStore.getState()/useStore.setState() 在工具函数、定时器中操作状态;
  6. 避免状态中存函数:状态应是可序列化的数据(函数、React 组件等不建议存入,可能导致持久化失败)。

七、常见问题

1. 组件不重渲染?

  • 原因1:订阅状态时返回新对象/数组(导致全等比较失败),需用 shallow 浅比较;

  • 原因2:修改状态时未用 set 函数(直接修改原状态不会触发更新);

  • 解决:

    javascript 复制代码
    // 错误:直接修改原状态(不触发更新)
    const store = useCounterStore.getState();
    store.count = 10;
    
    // 正确:用 set 函数修改
    useCounterStore.setState({ count: 10 });

2. 持久化状态不生效?

  • 检查状态是否可序列化(不能包含函数、Symbol 等);
  • 确认 getStorage 配置正确(localStorage/sessionStorage);
  • 清除浏览器缓存后重试。
相关推荐
Dovis(誓平步青云)32 分钟前
《QT学习第四篇:常见事件与UDP、TCP、文件系统、(锁、信号量、条件变量》
c语言·开发语言·汇编·qt
小陈同学呦8 小时前
前端如何处理订单状态导航的数据竞态问题
前端·javascript
开发者每周简报9 小时前
网海三部曲·无名宗师传
javascript·人工智能
isyangli_blog9 小时前
OpenDayLight (Carbon 版本) 启动与组件安装
开发语言·php
vb2008119 小时前
FastAPI APIRouter
开发语言·python
Benszen9 小时前
KVM虚拟化解决方案
开发语言·perl
会编程的土豆9 小时前
Go 语言反射(Reflection)详解
开发语言·后端·golang
東雪木9 小时前
多线程与并发编程 专属复习笔记
java·开发语言·笔记·java面试
杨充10 小时前
1.3 浮点型数据设计灵魂
开发语言·python·算法
噜噜噜阿鲁~10 小时前
python学习笔记 | 11.3、面向对象高级编程-多重继承
java·开发语言