Zustand 状态管理:轻量高效的 React 状态解决方案✨

54 阅读13分钟

深入浅出 Zustand:前端状态管理的「轻量王者」✨

如果说国家需要有中央银行统一管理货币发行与流通,那么我们的前端项目就需要中央状态管理系统来统筹全局数据。

在 React 生态中,Redux 曾是状态管理的「标准答案」,但繁琐的模板代码、复杂的中间件配置让开发者苦不堪言;Context+useState 虽轻量,却难逃重渲染、跨组件传值的痛点。

而 Zustand 的出现,就像为前端状态管理量身打造的「极简中央银行」—— 轻量、灵活、无侵入,只用几十行代码就能搞定复杂的全局状态管理,成为当下 React 项目中最受欢迎的状态管理方案之一。

一、Zustand 的核心认知 🍬

1. 什么是 Zustand?

Zustand 是由 React 核心团队成员开发的轻量级状态管理库,核心定位是「极简的全局状态管理方案」。如果把前端项目比作一家超市:

  • 全局状态就是超市的「库存数据」(比如商品数量、价格、库存状态);
  • Zustand 就是超市的「库存管理系统」,专门负责存储、修改、查询这些库存数据;
  • 组件就是超市的「各个货架 / 收银台」,通过这个系统获取最新库存,也能修改库存数据。

它摒弃了 Redux 的繁琐模板、Context 的嵌套限制,只用一个create方法就能创建全局状态仓库,组件调用像用普通 Hook 一样简单。

2. 为什么需要 Zustand?

前端项目中,「状态共享」是绕不开的需求:比如购物车数据需要在商品页、结算页、个人中心同步;用户登录状态需要在首页、详情页、设置页共用。没有统一的状态管理系统,会出现这些问题:

  • 组件间传值像「传话游戏」,多层嵌套的组件传值要层层透传(props drilling),代码冗余且易出错;
  • Context+useState 修改状态时,会导致所有消费 Context 的组件强制重渲染,性能损耗大;
  • Redux 需要写 action、reducer、dispatch 等大量模板代码,开发效率低。

而 Zustand 就像「快递直送」,全局状态可以直接被任意组件获取 / 修改,无需层层传递,也不会有多余的重渲染,完美解决以上痛点。

3. Zustand 的核心优势是什么?

  • 🚀 极致轻量:核心代码仅 2KB,无任何依赖,接入项目几乎不增加包体积;
  • 🎯 无侵入式:无需 Provider 包裹根组件,摆脱 Context 的嵌套地狱;
  • 🧠 极简 API:核心只有create方法,学习成本极低,上手即用;
  • ⚡ 性能优秀:细粒度状态订阅,只有用到的状态变化时组件才重渲染;
  • 🛠️ 灵活度高:原生支持异步状态修改、持久化存储,无需额外中间件;
  • 🔌 兼容性好:支持 React Class 组件、函数组件,也能和 TypeScript 完美适配。

二、安装 Zustand 核心包📦

Zustand 无任何依赖,直接安装核心包即可,npm/pnpm 都支持,在项目全局执行:

bash

npm install zustand 
# 推荐使用pnpm(速度更快、磁盘占用更少)
pnpm add zustand

三、构建 Store(状态容器)🏪

1. 核心概念

Store 是 Zustand 的核心,是一个全局唯一的状态容器,就像超市的「中央库存仓」,里面包含:

  • 项目的全局状态数据(比如计数器的count值);
  • 修改状态的方法(比如计数器的increment加 1、decrement减 1);
  • 衍生计算属性(比如根据count计算是否为偶数)。

2. 创建 Store 的基础写法

为了项目代码结构清晰,我们通常创建一个单独的文件夹(src/store/),统一组织管理全局状态;在 store 目录下按业务模块编写状态文件counter.ts`),结构如下:

plaintext

src/
└── store/
        └── counter.ts  # 计数器状态模块
        └── App.tsx

QQ20260116-111554.png

3. 简单的计数器案例实现

第一步:编写状态仓库(src/store/counter.ts)

typescript

import { create } from 'zustand';
import { persist } from 'zustand/middleware';

// 第一步:定义状态类型(TypeScript 必备,约束状态和方法)
interface CounterState {
    count: number; // 核心状态:计数器数值
    increment: () => void; // 方法:计数器加1
    decrement: () => void; // 方法:计数器减1
    reset: () => void; // 方法:重置计数器
}

// 第二步:创建状态仓库
const useCounterStore = create<CounterState>((set, get) => ({
    // 初始化状态
    count: 0,

    // 方法1:计数器加1(基于旧状态修改)
    increment: () => set((state) => ({ count: state.count + 1 })),

    // 方法2:计数器减1(基于旧状态修改)
    decrement: () => set((state) => ({ count: state.count - 1 })),

    // 方法3:重置计数器(直接修改状态)
    reset: () => set({ count: 0 }),
}));

// 导出自定义Hook,供组件使用
export default useCounterStore;
第二步:组件中使用状态(src/App.tsx)

tsx

import './App.css';
import useCounterStore from './store/counter';
import { useState } from 'react';

function App() {
  // 解构获取状态和方法
  const { count, increment, decrement, reset } = useCounterStore();

  return (
    <>
      {/* 点击按钮触发加1,同时展示当前数值 */}
      <button onClick={increment}>
        count is {count}
      </button>
      {/* 点击按钮触发减1 */}
      <button onClick={decrement}>
        decrement
      </button>
      {/* 点击按钮重置计数器 */}
      <button onClick={reset}>
        reset
      </button>
    </>
  );
}

export default App;
代码逻辑详解
  1. 状态定义层:通过interface CounterState约束了计数器的状态(count)和修改方法(increment/decrement/reset),TypeScript 加持下,代码提示和类型校验更友好;
  2. 状态创建层:调用create方法传入回调函数,回调函数返回包含「初始状态 + 修改方法」的对象,create最终返回一个自定义 Hook(useCounterStore);
  3. 组件使用层:在App组件中直接解构useCounterStore的返回值,获取count状态和修改方法,绑定到按钮的点击事件,实现计数器的核心功能。
代码效果展示

QQ20260116-111928.gif

四、Zustand 核心重点🔍

仔细阅读并理解上面案例的代码,我们将根据案例代码拆分并讲解 Zustand 核心知识点。

1. 状态仓库的创建工厂:create 方法

在 Zustand 中,create函数是整个状态管理库的核心。它就像「状态仓库的工厂」,用于创建一个可被 React 组件使用的状态 store(状态容器),并返回一个自定义 Hook(如案例中的useCounterStore)。

这个 Hook 有两个核心特性:

  • 可在任意组件中调用,无需像 Redux/Context 那样用 Provider 包裹根组件,彻底摆脱 React Context 的限制;
// Provider 包裹跟组件示例

// 1. 创建 Context
const ThemeContext = createContext();

// 2. 用 Provider 包裹组件树,提供数据
function App() {
  return (
    <ThemeContext.Provider value="dark">
      <ChildComponent />
    </ThemeContext.Provider>
  );
}

// 3. 子组件必须在 Provider 内部才能消费数据
function ChildComponent() {
  const theme = useContext(ThemeContext); // ← 必须在 Provider 下才能拿到值
  return <div>当前主题:{theme}</div>;
}
  • 调用后能直接获取状态或触发状态更新,用法和普通 React Hook 完全一致,学习成本几乎为零。

2. 核心参数说明:create 回调的两个内置函数

调用create()时传入的回调函数,会被自动注入两个核心内置方法(setget),专门用来操作状态,这是 Zustand 的核心,必须掌握:

(1)set:修改状态的核心方法

set方法的唯一作用是更新 store 中的状态,触发依赖该状态的组件重渲染。所有状态更新都必须通过set完成,不允许直接修改状态(比如state.count = 1是错误写法)。

(2)get:获取当前最新状态

get方法的作用是读取 store 中的当前最新状态,可以在任意修改方法中调用,用于「基于其他状态做逻辑处理」。比如我们想给计数器加一个「加倍」方法:

typescript

// 加倍方法
double: () => {
  const currentCount = get().count; // 获取当前最新count值
  set({ count: currentCount * 2 }); // count 值乘以2
}

3. 核心重点:修改状态的 3 种主流方式

Zustand 提供了 3 种修改状态的写法,覆盖同步 / 异步、简单 / 复杂所有业务场景,无优先级,按需使用,都是官方推荐的标准写法。

第一种:直接修改状态(最简单,推荐简单场景)

适用于:不需要依赖旧状态、直接赋值更新的场景(如重置计数器、修改用户名、开关状态等)。

语法:set({ 要修改的状态键: 新值 })

代码举例(基于计数器案例):

typescript

// 重置计数器:直接将count设为0,无需依赖旧值
reset: () => set({ count: 0 }),

// 拓展:设置固定值(比如直接设为10)
setFixed: () => set({ count: 10 }),

详解:set方法接收一个对象,对象的键是要修改的状态名(count),值是新的状态值(0/10),调用后状态会立即更新,依赖该状态的组件会重渲染。

第二种:基于旧状态修改(最常用,推荐核心场景)

适用于:需要依赖状态的旧值更新新值的场景(如计数累加、数组追加、对象属性修改等),这是项目中使用频率最高的写法。

语法:set( (prevState) => ({ 要修改的状态键: 基于prevState的新值 }) )

其中prevState是 store 中当前最新的完整状态对象,只读不可直接修改;回调函数返回一个对象,代表要更新的状态键值对。

代码举例(基于计数器案例):

typescript

// 计数器加1:依赖旧的count值,加1后作为新值
increment: () => set((prevState) => ({ count: prevState.count + 1 })),

// 计数器减1:同理,依赖旧值减1
decrement: () => set((prevState) => ({ count: prevState.count - 1 })),

// 拓展:数组追加(通用场景)
// 假设状态中有一个list数组,新增元素
addItem: () => set((prev) => ({
  list: [...prev.list, '新元素'] // 基于旧数组创建新数组,避免直接修改原数组
})),

详解:set方法接收一个回调函数,参数prevState是更新前的状态快照,我们基于这个快照计算新值,返回新的状态对象。这种写法保证了状态更新的原子性,避免并发修改导致的错误。

第三种:异步修改状态(原生支持,无需中间件,重中之重)

核心亮点:Zustand 对异步的支持是「原生级」的,不需要任何中间件(比如 Redux 需要 redux-thunk/redux-saga)。适用于:所有需要异步操作后更新状态的场景(接口请求、定时器、本地存储读取等)。

语法:直接在方法中定义async函数,在异步逻辑完成后,调用set方法更新状态即可。

代码举例(基于计数器案例,模拟异步请求后更新状态):

typescript

// 异步加载初始值(比如从接口获取计数器初始值)
fetchInitCount: async () => {
  // 模拟接口请求
  const res = await fetch('https://api.example.com/count');
  const data = await res.json();
  // 异步操作完成后,调用set更新状态
  set({ count: data.initCount });
},

// 异步加1(模拟延迟操作)
asyncIncrement: async () => {
  // 模拟异步延迟(比如接口提交后加1)
  await new Promise(resolve => setTimeout(resolve, 1000));
  // 基于旧状态修改
  set((prev) => ({ count: prev.count + 1 }));
}

详解:异步方法中,我们可以先执行异步逻辑(接口请求、定时器),等结果返回后,再调用set方法更新状态,写法和普通异步代码完全一致,没有额外学习成本。

4. 组件中使用 Store(订阅状态 / 调用方法)

组件中使用 Zustand 的状态和方法,是无侵入式的,直接调用创建好的useStore即可,有两种核心用法:

用法一:通过 selector 选择需要的状态 / 方法(推荐,性能最优)

核心语法:useStore( state => state.xxx )

代码举例(基于计数器案例):

tsx

import useCounterStore from './store/counter';

function App() {
  // 只订阅count状态:只有count变化时,组件才重渲染
  // 其中,useCounterStore 接受一个selector 函数作为返回值
  const count = useCounterStore(state => state.count);
  // 只订阅increment方法:方法引用不会变化,组件不会因方法重渲染
  const increment = useCounterStore(state => state.increment);

  return (
    <button onClick={increment}>count is {count}</button>
  );
}

核心优势:细粒度订阅,组件只会订阅「selector 返回的这个值」,只有这个值变化时,组件才会重渲染,性能最优,项目中优先使用这种写法。

用法二:解构多个状态 / 方法(便捷,但有坑)

如果组件需要用到多个状态 / 方法,手动写多个 selector 会繁琐,可直接解构:

tsx

// 便捷写法,但有重渲染坑点
const { count, increment, decrement } = useCounterStore();
解构的「重渲染坑点」(高频问题,必看必解决)

问题原因:每次调用useCounterStore()都会返回一个新的对象,即使状态没变化,组件也会因为「对象引用变化」触发重渲染,导致性能损耗。

解决方案:使用shallow浅比较,让 Zustand 只比较对象的属性值,而非引用。

用法步骤:

  1. 从 zustand 中导入shallow
  2. useStore中传入第二个参数shallow

代码举例:

tsx

import { shallow } from 'zustand/shallow'; // 第一步:导入shallow
import useCounterStore from './store/counter';

function App() {
  // 第二步:传入shallow作为第二个参数
  const { count, increment, decrement } = useCounterStore(
    state => ({
      count: state.count,
      increment: state.increment,
      decrement: state.decrement
    }),
    shallow // 浅比较:只有属性值变化时,才触发重渲染
  );

  return (
    <>
      <button onClick={increment}>count is {count}</button>
      <button onClick={decrement}>decrement</button>
    </>
  );
}

注释详解:shallow会对 selector 返回的对象做浅比较,只有当countincrementdecrement发生变化时,组件才会重渲染,解决了引用变化导致的无效重渲染问题。

5. 持久化能力:persist 中间件(本地存储)💾

persist是 Zustand 官方提供的中间件,核心功能是:自动将 Store 的状态保存到持久化存储(如 localStorage、sessionStorage),并在页面刷新或重新加载时自动恢复。

代码举例(基于计数器案例,添加持久化):

typescript

import { create } from 'zustand';
import { persist } from 'zustand/middleware'; // 导入persist中间件

interface CounterState {
    count: number;
    increment: () => void;
    decrement: () => void;
    reset: () => void;
}

// 使用persist包裹状态逻辑
const useCounterStore = create<CounterState>()(
  persist(
    (set, get) => ({
      count: 0,
      increment: () => set((state) => ({ count: state.count + 1 })),
      decrement: () => set((state) => ({ count: state.count - 1 })),
      reset: () => set({ count: 0 }),
    }),
    {
      name: 'counter-localStorage', // 本地存储的key(必填)
      // 可选:指定存储方式,默认localStorage,也可设为sessionStorage
      // storage: sessionStorage,
    }
  )
);

export default useCounterStore;

代码逻辑详解:

  1. 导入persist中间件:从zustand/middleware中导入,这是官方内置的中间件,无需额外安装;
  2. 包裹状态逻辑:将create的回调函数用persist包裹,persist接收两个参数 —— 状态逻辑函数、配置对象;
  3. 配置存储 key:name字段是本地存储的唯一标识(比如counter-localStorage),页面刷新后,Zustand 会自动从 localStorage 中读取该 key 对应的数值,恢复count状态;
  4. 可选配置:storage字段可指定存储方式,默认localStorage,也可设置为sessionStorage,满足不同的持久化需求。

持久化效果展示:

QQ20260116-143616.gif

五、核心知识总结(面试官会问)📝

  1. Zustand 的核心优势:轻量无依赖、无 Provider、细粒度订阅、原生支持异步、TypeScript 友好;
  2. create 方法的作用:创建状态仓库,返回自定义 Hook,无需 Provider 包裹;
  3. set 和 get 的区别:set 用于修改状态(必须通过 set 更新,不能直接修改),get 用于获取当前最新状态;
  4. 修改状态的三种方式:直接修改(简单场景)、基于旧状态修改(核心场景)、异步修改(异步场景);
  5. 组件使用 Store 的性能优化:优先使用 selector 细粒度订阅,解构时配合 shallow 浅比较避免无效重渲染;
  6. 持久化实现:通过 persist 中间件,配置 name 和 storage,实现状态本地存储与自动恢复。

六、结语🎈

Zustand 以「极简、灵活、高性能」的特性,成为 React 状态管理的优选方案。它没有 Redux 的繁琐模板,也没有 Context 的性能问题,只用几行代码就能实现全局状态管理,无论是小型项目还是大型应用,都能轻松应对。

本文通过计数器案例,从核心概念、安装、Store 构建、核心 API 到性能优化、持久化,完整拆解了 Zustand 的使用方法。希望这篇文章能帮助你彻底掌握 Zustand,让前端状态管理变得简单又高效。记住:最好的学习方式是动手实践,把案例代码敲一遍,结合自己的项目场景改造,你会发现 Zustand 的魅力所在!