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);
  • 清除浏览器缓存后重试。
相关推荐
8***B1 小时前
Java自然语言处理
java·开发语言·自然语言处理
赛贝维权申诉1 小时前
30款亚马逊热销儿童玩具,美国外观专利侵权预警!
java·开发语言
IT·小灰灰1 小时前
基于Python的机器学习/数据分析环境搭建完全指南
开发语言·人工智能·python·算法·机器学习·数据分析
2***B4491 小时前
JavaScript语音识别案例
开发语言·javascript·语音识别
未来之窗软件服务2 小时前
幽冥大陆(二十九)监控平台协议常见地址——东方仙盟练气期
开发语言·php·东方仙盟·东方仙盟sdk·监控协议
是你的小橘呀2 小时前
JavaScript 原型链解密:原来 proto 和 prototype 这么好懂
前端·javascript·前端框架
ohyeah2 小时前
使用 LocalStorage 实现本地待办事项(To-Do)列表
前端·javascript
Jing_Rainbow2 小时前
【前端三剑客-6/Lesson11(2025-10-28)构建现代响应式网页:从 HTML 到 CSS 弹性布局再到 JavaScript 交互的完整指南 🌈
前端·javascript
6***37942 小时前
JavaScript虚拟现实开发
开发语言·javascript·vr