一文教你五分钟学会Zustand,React状态管理更加方便!

Zustand 快速入门

说明:本笔记源于我学习B站up"一线柏拉图"的视频
www.bilibili.com/video/BV1Tr...

前置知识:基本掌握React语法,包括useState,useReducer,useContext,状态提升,组件间共享状态等

Zustand(德语"状态")是一个轻量、快速、无模板的 React 状态管理库。它基于 Hook,API 设计极简。


为什么需要 Zustand?

  • React 本身已经提供了状态管理,但随着应用变复杂,内置方案会暴露出各种痛点。
  • 多个组件需要共享用户信息时Context和Provider繁琐
  • 更新逻辑散落各组件,状态修改到处都有,难以追踪
  • 无法在组件外部读取/修改,灵活性差。

1. 安装

bash 复制代码
npm install zustand

2. 创建存储器 Store

  • Store 就是一个自定义 Hook,用 create 函数创建,用来存储定义好的状态和更新函数。
  • 状态和更新函数都定义在一起,方便维护和测试。
  • 在实际项目中往往会有一个专门的文件夹用于存放你定义的所有的Stores。
javascript 复制代码
import { create } from 'zustand'

//例如定义熊bear
const useBearStore = create((set) => ({
  bears: 0,//定义状态,可以定义多个状态
  owner: "Yue",
  //定义更新函数
  increase: () => set((state) => ({ bears: state.bears + 1 })),
  decrease: () => set((state) => ({ bears: state.bears - 1 })),
  reset: () => set({ bears: 0 }),
}))

set 是创建 store 时,create 函数自动注入的第一个参数,专门用来更新状态。你可以把它理解为 Zustand 版的 setState。它接收一个对象或函数。函数可接收当前状态 state,用于基于旧值更新。


3. 在组件中使用

直接调用自定义 Hook 即可获取状态和操作方法,无需useContext的Provider包裹

js 复制代码
import useBearStore from './store/bearStore'//导入定义好的Store, 导入的useBearStore叫做Store选择器

function BearCounter() {
  // 订阅多个字段
  const bears = useBearStore((state) => state.bears)
   const owner = useBearStore((state) => state.owner)
  return <h1>🐻 {bears} 只熊</h1>
}

function Controls() {
  // 订阅方法(选择器提取多个值)
  const increase = useBearStore((state) => state.increase)
  const decrease = useBearStore((state) => state.decrease)
  const reset = useBearStore((state) => state.reset)

  return (
    <div>
      <button onClick={increase}>+1</button>
      <button onClick={decrease}>-1</button>
      <button onClick={reset}>重置</button>
    </div>
  )
}

// 合并在一个组件中
function App() {
  return (
    <>
      <BearCounter />
      <Controls />
    </>
  )
}

解构赋值(简化)

js 复制代码
 const { bears, owner, increase, decrease, reset } = useBearStore(
    {
      bears: state.bears,
      owner: state.owner,
      increase: state.increase,
      decrease: state.decrease,
      reset: state.reset,
    })

4. 更新状态的几种方式

  • 直接传对象(合并,非覆盖)
  • 传入函数 (接收 state,返回部分状态)
  • 使用 set 的第二个参数 :设为 true 会替换整个状态(慎用)
javascript 复制代码
set({ bears: 5 })                     // 合并
set((state) => ({ bears: state.bears + 1 })) // 函数更新
set({ bears: 0 }, true)               // 替换整个状态(其他字段会丢失)

5. 异步操作

直接在 set 中编写异步逻辑即可,就像普通函数一样。

javascript 复制代码
//例如发送网络请求
const useFishStore = create((set) => ({
  fish: [],
  loading: false,
  fetchFish: async () => {
    set({ loading: true })
    const res = await fetch('/api/fish')
    const fish = await res.json()
    set({ fish, loading: false })
  },
}))

6. 选择器与性能优化

通过选择器只订阅需要的部分,避免不必要的重渲染。

js 复制代码
// 只有 bears 变了才重渲染
const bears = useBearStore((state) => state.bears)

// 取多个值用 shallow 比较(需额外导入)
import { useShallow } from 'zustand/shallow'

const { bears, owner, increase, decrease, reset } = useBearStore(
    useShallow((state) => ({
      bears: state.bears,
      owner: state.owner,
      increase: state.increase,
      decrease: state.decrease,
      reset: state.reset,
    }))
  )
  • 当你用选择器返回一个对象或数组时,每次渲染都会创建新的引用,导致组件即使数据没变也会重渲染;
  • useShallow 是 Zustand 提供的一个辅助 Hook,用于浅比较选择器的返回值,避免不必要的重渲染。

7. 中间件

  • 中间件是包装 store 创建过程的函数,通过中间件函数包裹 store 定义来给 store 添加额外功能(如持久化、日志、调试等),而不修改业务逻辑。
  • 可以理解为:在 create 和你的 store 定义之间加一层拦截/增强。
  • Zustand 提供了常用中间件,组合使用时注意顺序(一般从外到内:immer > devtools> subscribeWithSelector > persist > store)。

下面是这四个 Zustand 中间件的详细讲解,从作用、使用场景到如何组合使用,一步步说明。


7.1 immer

作用

让你的状态更新方式更简单: 让你用 "直接修改" 的写法来处理不可变数据,Zustand 内部会自动转为不可变更新。

传统写法 vs immer 写法
javascript 复制代码
// ❌ 传统 immutable 写法(一堆展开运算符)
set((state) => ({
  user: {
    ...state.user,
    profile: {
      ...state.user.profile,
      name: '小明'
    }
  }
}))

// ✅ immer 写法:直接修改, 本质是传统方式的封装
set((state) => {
  state.user.profile.name = '小明'
})
安装与使用
bash 复制代码
npm install immer
javascript 复制代码
import { create } from 'zustand'
import { immer } from 'zustand/middleware/immer'

const useStore = create(
  immer((set) => ({
    user: { name: '', age: 0 },
    updateName: (name) =>
      set((state) => {
        state.user.name = name // 直接修改
      }),
  }))
)
适用场景
  • 状态嵌套较深(对象/数组)
  • 烦透了各种 ... 展开运算符
  • 更新逻辑复杂且集中在某个深层字段

7.2 devtools

作用

连接 Redux DevTools 浏览器插件,可视化调试状态变化(历史回放、时间旅行)。

Redux DevTools 浏览器插件:这是一个专门用于调试状态管理的浏览器扩展,虽然名字带 Redux,但 Zustand、MobX、Recoil 等都能用它。

安装

无需额外安装,Zustand 内置。

基础用法
javascript 复制代码
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'

const useStore = create(
  devtools(
    (set) => ({
      count: 0,
      inc: () => set((s) => ({ count: s.count + 1 }), false, 'increment'),
    }),
    { name: 'CounterStore' } // DevTools 中显示的名字
  )
)

set 的第三个参数是 action 名称,方便 DevTools 里查看。

适用场景
  • 开发和调试阶段排查状态变更
  • 想知道"什么时候、谁(哪个 action)改了状态"
  • 需要回放时间、查看历史

7.3 subscribe(原生方法)和 subscribeWithSelector(中间件)

7.3.1 原生 subscribe

Zustand 的每个 store 默认自带 subscribe 方法,用于监听整个 store 的变化

作用

subscribe 允许你在组件外部(普通 JS 模块、工具函数、WebSocket 连接等)也能监听状态变化,用于副作用管理,而useEffect 只能在组件内使用。

javascript 复制代码
const useStore = create((set) => ({
  count: 0,
  name: 'Zustand'
}))

// 原生 subscribe:任何字段变化都会触发
const unsub = useStore.subscribe((state, prevState) => {
  console.log('有状态变了')
  console.log('旧状态:', prevState)
  console.log('新状态:', state)
})

特点

  • 只能监听整个 store,无法指定只监听某个字段
  • 回调参数是 (newState, prevState)
  • 适合"只要变化就执行"的场景

7.3.2 subscribeWithSelector 中间件
作用

原生 subscribe 无法选择性地监听特定字段。加上这个中间件后,subscribe 方法被增强,可以传入选择器函数,只在选定数据变化时才触发。

javascript 复制代码
import { subscribeWithSelector } from 'zustand/middleware'

const useStore = create(
  subscribeWithSelector((set) => ({
    count: 0,
    name: 'Zustand'
  }))
)

// 增强后的 subscribe:只监听 count
useStore.subscribe(
  (state) => state.count,          // 选择器
  (newCount, prevCount) => {       // 回调只接收选定的值,新值与旧值
    console.log(`count: ${prevCount} → ${newCount}`)
  }
)

特点

  • 第一个参数是选择器,第二个是回调函数
  • 回调参数是 (selectedValue, prevSelectedValue),更精准
  • 使用 Object.is 比较选择器返回值,只有真正变化才触发
安装

也是 Zustand 内置。

具体适用场景
  • React 组件外部需要监听特定字段变化
  • 实现自定义事件/副作用(如播放声音、发送分析)
  • WebSocket 状态绑定

7.3.3 核心区别对比
特性 原生 subscribe subscribeWithSelector
需要中间件 ❌ 不需要 ✅ 需要显式添加
监听范围 整个 store 可选择特定字段
回调参数 (state, prevState) (selected, prevSelected)
触发频率 任何字段变化都触发 只有选择的字段变化才触发
性能 粗粒度 精细控制

7.3.4 使用场景例举对比
javascript 复制代码
// 场景1:任何状态变化都要记录(原生 subscribe 就够)
useStore.subscribe((state) => {
  console.log('快照日志:', state)
})

// 场景2:只在 token 变化时重连 WebSocket
useStore.subscribe(
  (s) => s.token,        // 需要 subscribeWithSelector
  (token) => {
    if (token) connectWS(token)
    else disconnectWS()
  }
)

// 场景3:只在 theme 变化时操作 DOM
useStore.subscribe(
  (s) => s.theme,        // 需要 subscribeWithSelector
  (theme) => {
    document.documentElement.dataset.theme = theme
  }
)

7.3.5 联系:作用相同,能力不同(了解即可)
  • 本质相同:都是监听状态变化的底层 API,都返回取消订阅函数
  • 能力递进subscribeWithSelector 是原生 subscribe增强版
  • 内部关系 :Zustand 内部实现中,subscribeWithSelector 包装了原生 subscribe,用 Object.is 做比较
javascript 复制代码
// 概念上的伪代码
function subscribeWithSelector(store) {
  return (selector, callback) => {
    let prevValue = selector(store.getState())
    
    // 内部还是调用原生 subscribe
    store.subscribe((state) => {
      const newValue = selector(state)
      if (!Object.is(newValue, prevValue)) {
        callback(newValue, prevValue)
        prevValue = newValue
      }
    })
  }
}

7.3.6 组件外监听 vs 组件内选择器
环境 用什么
React 组件内 useStore(selector)
组件外,监听整个 store 原生 subscribe
组件外,监听特定字段 subscribeWithSelector

总结

原生 subscribe 是基础款,粗粒度监听全部变化。subscribeWithSelector 是通过中间件增强的精准版,让你能选择性监听特定字段,避免无用的回调触发。想要细粒度控制在组件外监听,用后者就对了。


7.4 persist

作用

自动将状态保存到 localStorage / sessionStorage 等,刷新页面后自动恢复。

安装

内置,无需安装。

基础用法
javascript 复制代码
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'

const useStore = create(
  persist(
    (set) => ({
      count: 0,
      inc: () => set((s) => ({ count: s.count + 1 })),
    }),
    {
      name: 'my-counter',//localStorage 的 key(键名)
      storage: createJSONStorage(() => localStorage), // 默认就是这个,可换sessionStorage等,修改Store存储在浏览器的位置
    }
  )
)
进阶配置(感兴趣可学)
javascript 复制代码
persist(store, {
  name: 'app-store',
  storage: createJSONStorage(() => sessionStorage), // 用 sessionStorage
  partialize: (state) => ({ token: state.token }), // 只持久化 token
  version: 1,
  migrate: (persistedState, version) => {
    // 版本迁移逻辑
    return persistedState
  },
})
适用场景
  • 用户偏好(主题、语言)
  • 购物车、草稿
  • JWT token

7.5 中间件组合顺序

javascript 复制代码
import { create } from 'zustand'
import { persist, devtools, subscribeWithSelector } from 'zustand/middleware'
import { immer } from 'zustand/middleware/immer'

const useStore = create(
  devtools(                              // 1. 最外:调试
    subscribeWithSelector(               // 2. 允许选择性订阅
      persist(                           // 3. 持久化
        immer((set) => ({                // 4. 深层更新 + store
          user: { name: '小明', age: 18 },
          incAge: () =>
            set((s) => {
              s.user.age += 1
            }),
        })),
        { name: 'my-store' }
      )
    ),
    { name: 'AppStore' }
  )
)

// 在组件外选择性监听
useStore.subscribe(
  (s) => s.user.age,
  (newAge) => console.log('年龄变成:', newAge)
)

8. 在 React 之外使用

Store 返回的 Hook 上挂载了 getStatesetState,可以直接在 React 外部读取/修改状态。

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

const useStore = create((set) => ({
  count: 0,
  user: { name: '小明', age: 18 },
  inc: () => set((s) => ({ count: s.count + 1 })),
}))

// ✅ 在任何地方获取当前状态,不再只限于组件内
const currentState = useStore.getState()
console.log(currentState.count)  // 0
console.log(currentState.user.name)  // '小明'

//setState不用受已定义的更新函数的影响,做到随用随定义
// ✅ 在组件外直接更新
useStore.setState({ count: 10 })

// ✅ 传入函数(基于旧状态的更新)
useStore.setState((prev) => ({ count: prev.count + 1 }))

// ✅ 替换整个 state(第二个参数 true)
useStore.setState({ count: 0 }, true)

9. 进阶学习

相关推荐
空中海3 小时前
03 性能、动画与 React Native 新架构
react native·react.js·架构
空中海4 小时前
02 React Native状态、导航、数据流与设备能力
javascript·react native·react.js
空中海5 小时前
04 React Native工程化、质量、发布与生态选型
javascript·react native·react.js
sealaugh328 小时前
react native(学习笔记第三课) 英语打卡微应用(2)-从上传图片开始
笔记·学习·react native
空中海9 小时前
05 React Native架构设计、主线项目与专家实践
javascript·react native·react.js
一个扣子2 天前
降低 Android APK 体积:Hermes 的字节码格式与资源压缩
react native·字节码·构建优化·包体积优化·android性能·hermes·apk瘦身
cn_mengbei3 天前
用React Native开发OpenHarmony应用:Reanimated共享元素过渡
javascript·react native·react.js
祖国的好青年5 天前
VS Code 搭建 React Native 开发环境(Windows 实战指南)
android·windows·react native·react.js
一个扣子5 天前
性能面板解读:通过 Hermes Runtime 测量函数执行耗时
react native·chrome devtools·hermes·性能面板·函数耗时·performance api