Zustand 深度解析:原理、源码与最佳实践

从 Redux 的繁琐到 Zustand 的优雅:探索 2025 年最受欢迎的 React 状态管理方案

目录

  1. 为什么选择 Zustand
  2. 核心原理与架构
  3. 源码深度解析
  4. 实战应用模式
  5. 中间件系统
  6. 性能优化
  7. 最佳实践

为什么选择 Zustand

状态管理的演进

在 React 生态中,状态管理经历了从 Redux 的"样板代码地狱"到现代化轻量方案的转变。2025 年,Zustand 以其简洁性和强大功能成为开发者的首选,GitHub 上获得 5.5万+ stars ,周下载量达到 1276万+

Zustand vs 传统方案

特性 Redux Context API Zustand Jotai
样板代码 极少
学习曲线 陡峭 平缓 平缓 中等
性能 优秀 一般 优秀 优秀
Bundle 大小 ~15KB 0 (内置) ~1.2KB ~3KB
中间件支持 强大 灵活 有限
DevTools 完善 支持 支持
TypeScript 优秀 良好 优秀 优秀

Zustand 的核心优势

  1. 极简 API:无需 Provider、Reducer、Action Types
  2. 高性能 :基于 useSyncExternalStore,避免 Context 的性能陷阱
  3. 灵活架构:支持多 Store 模式,更符合模块化开发
  4. 强大中间件:内置 persist、devtools、immer 等中间件
  5. TypeScript 友好:完善的类型推断,无需手写类型

适用场景

根据 2025 年社区实践,Zustand 最适合:

  • 中大型应用:需要全局状态但不想引入 Redux 复杂性
  • 快速迭代项目:减少样板代码,提升开发效率
  • 性能敏感场景:细粒度订阅,避免不必要的重渲染
  • 团队协作:代码简洁,降低新人学习成本

核心原理与架构

设计哲学

Zustand(德语"状态")的设计遵循三大原则:

  1. 单向数据流:State → View → Action → State
  2. 不可变更新 :通过 set 函数创建新状态
  3. 选择器订阅:组件只订阅需要的状态切片

核心架构

scss 复制代码
┌─────────────────────────────────────────┐
│           Zustand Store                 │
├─────────────────────────────────────────┤
│  Vanilla Core (vanilla.ts)              │
│  ├─ createStore()                       │
│  │  ├─ State Storage                    │
│  │  ├─ Listeners Set                    │
│  │  └─ setState/getState/subscribe      │
│  │                                       │
│  React Binding (react.ts)               │
│  └─ create()                            │
│     └─ useSyncExternalStore()           │
│                                          │
│  Middleware Layer                       │
│  ├─ persist (localStorage/sessionStorage)│
│  ├─ devtools (Redux DevTools)           │
│  ├─ immer (不可变更新)                  │
│  └─ subscribeWithSelector               │
└─────────────────────────────────────────┘

关键技术:useSyncExternalStore

Zustand 基于 React 18 的 useSyncExternalStore Hook,这是 React 官方提供的订阅外部状态的标准方案:

TypeScript 复制代码
// React 18 的核心 Hook
useSyncExternalStore(
  subscribe,    // 订阅函数
  getSnapshot,  // 获取当前状态
  getServerSnapshot // SSR 时的服务端状态
)

优势

  • 并发模式兼容:支持 React 18 的并发特性
  • 避免撕裂:保证状态一致性(tearing-free)
  • 性能优化:只在订阅的状态变化时触发重渲染

源码深度解析

1. Vanilla 核心实现(~43 行代码)

Zustand 的核心极其精简,让我们逐步解析:

TypeScript 复制代码
// zustand/vanilla.ts (简化版)
export const createStore = (createState) => {
  let state                           // 状态存储
  const listeners = new Set()         // 订阅者集合

  // 设置状态
  const setState = (partial, replace) => {
    const nextState = replace 
      ? partial 
      : Object.assign({}, state, partial)
    
    if (!Object.is(nextState, state)) {
      const previousState = state
      state = nextState
      listeners.forEach((listener) => listener(state, previousState))
    }
  }

  // 获取状态
  const getState = () => state

  // 订阅状态变化
  const subscribe = (listener) => {
    listeners.add(listener)
    return () => listeners.delete(listener) // 返回取消订阅函数
  }

  // 销毁 Store
  const destroy = () => listeners.clear()

  const api = { setState, getState, subscribe, destroy }
  state = createState(setState, getState, api)
  
  return api
}

核心机制

  1. 闭包存储状态state 变量通过闭包保存在内存中
  2. 发布-订阅模式listeners Set 管理所有订阅者
  3. 浅比较优化Object.is() 避免无意义的更新
  4. 不可变更新Object.assign() 创建新对象

2. React 绑定层

TypeScript 复制代码
// zustand/react.ts (简化版)
import { useSyncExternalStore } from 'react'
import { createStore } from './vanilla'

export const create = (createState) => {
  const api = createStore(createState)

  const useBoundStore = (selector = api.getState, equalityFn = Object.is) => {
    // 核心:使用 React 18 的 useSyncExternalStore
    const slice = useSyncExternalStore(
      api.subscribe,                    // 订阅函数
      () => selector(api.getState()),   // 获取选中的状态切片
      () => selector(api.getState())    // SSR 时的状态
    )
    
    return slice
  }

  Object.assign(useBoundStore, api)
  return useBoundStore
}

关键点

  • 选择器模式selector 函数提取需要的状态切片
  • 相等性检查equalityFn 决定是否触发重渲染(默认 Object.is
  • API 继承useBoundStore 同时拥有 Hook 和 Store API

3. 中间件实现原理

Zustand 的中间件本质是高阶函数 ,包装 createState

TypeScript 复制代码
// 中间件类型定义
type StateCreator<t> = (
  set: SetState<t>,
  get: GetState<t>,
  api: StoreApi<t>
) => T

type Middleware<t> = (
  createState: StateCreator<t>
) => StateCreator<t>

// 示例:日志中间件
const logger = (createState) => (set, get, api) => {
  const loggedSet = (args) => {
    console.log('Previous State:', get())
    set(args)
    console.log('New State:', get())
  }
  
  return createState(loggedSet, get, api)
}

// 使用
const useStore = create(logger((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 }))
})))

中间件链式调用

TypeScript 复制代码
// 多个中间件组合
const useStore = create(
  devtools(
    persist(
      immer((set) => ({
        user: { name: '', age: 0 },
        updateName: (name) => set((state) => {
          state.user.name = name  // immer 支持直接修改
        })
      })),
      { name: 'user-store' }  // persist 配置
    )
  )
)

4. Persist 中间件源码解析

TypeScript 复制代码
// zustand/middleware/persist.ts (核心逻辑)
export const persist = (config, options) => (set, get, api) => {
  const { name, storage = localStorage } = options

  // 从存储中恢复状态
  const hydrate = () => {
    const storedValue = storage.getItem(name)
    if (storedValue) {
      const parsedValue = JSON.parse(storedValue)
      set(parsedValue, true) // replace 模式
    }
  }

  // 订阅状态变化,自动保存
  api.subscribe((state) => {
    storage.setItem(name, JSON.stringify(state))
  })

  // 初始化时恢复
  hydrate()

  return config(set, get, api)
}

持久化流程

  1. 初始化 :从 localStorage 读取并恢复状态
  2. 自动保存:订阅状态变化,每次更新自动写入存储
  3. 序列化 :使用 JSON.stringify/parse(可自定义)

实战应用模式

1. 基础 Store 创建

TypeScript 复制代码
// stores/counterStore.ts
import { create } from 'zustand'

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

export const useCounterStore = create<counterstate>((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
  reset: () => set({ count: 0 })
}))

// 组件中使用
function Counter() {
  const count = useCounterStore((state) => state.count)
  const increment = useCounterStore((state) => state.increment)
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onclick="{increment}">+1</button>
    </div>
  )
}
</counterstate>

2. 异步操作处理

TypeScript 复制代码
// stores/userStore.ts
import { create } from 'zustand'

interface User {
  id: string
  name: string
  email: string
}

interface UserState {
  user: User | null
  loading: boolean
  error: string | null
  fetchUser: (id: string) => Promise<void>
  updateUser: (data: Partial<user>) => Promise<void>
}

export const useUserStore = create<userstate>((set, get) => ({
  user: null,
  loading: false,
  error: null,

  fetchUser: async (id) => {
    set({ loading: true, error: null })
    try {
      const response = await fetch(`/api/users/${id}`)
      const user = await response.json()
      set({ user, loading: false })
    } catch (error) {
      set({ error: error.message, loading: false })
    }
  },

  updateUser: async (data) => {
    const { user } = get()
    if (!user) return

    set({ loading: true })
    try {
      const response = await fetch(`/api/users/${user.id}`, {
        method: 'PATCH',
        body: JSON.stringify(data)
      })
      const updatedUser = await response.json()
      set({ user: updatedUser, loading: false })
    } catch (error) {
      set({ error: error.message, loading: false })
    }
  }
}))

3. 模块化 Store(推荐大型项目)

TypeScript

typescript 复制代码
// stores/slices/authSlice.ts
export interface AuthSlice {
  token: string | null
  isAuthenticated: boolean
  login: (token: string) => void
  logout: () => void
}

export const createAuthSlice = (set): AuthSlice => ({
  token: null,
  isAuthenticated: false,
  login: (token) => set({ token, isAuthenticated: true }),
  logout: () => set({ token: null, isAuthenticated: false })
})

// stores/slices/profileSlice.ts
export interface ProfileSlice {
  profile: User | null
  updateProfile: (data: Partial<user>) => void
}

export const createProfileSlice = (set): ProfileSlice => ({
  profile: null,
  updateProfile: (data) => set((state) => ({
    profile: { ...state.profile, ...data }
  }))
})

// stores/index.ts - 组合 Store
import { create } from 'zustand'
import { createAuthSlice, AuthSlice } from './slices/authSlice'
import { createProfileSlice, ProfileSlice } from './slices/profileSlice'

type StoreState = AuthSlice & ProfileSlice

export const useStore = create<storestate>()((...a) => ({
  ...createAuthSlice(...a),
  ...createProfileSlice(...a)
}))

4. 计算属性(Computed Values)

TypeScript 复制代码
// stores/cartStore.ts
import { create } from 'zustand'

interface CartItem {
  id: string
  name: string
  price: number
  quantity: number
}

interface CartState {
  items: CartItem[]
  addItem: (item: CartItem) => void
  removeItem: (id: string) => void
  // 计算属性通过 selector 实现
}

export const useCartStore = create<cartstate>((set) => ({
  items: [],
  addItem: (item) => set((state) => ({
    items: [...state.items, item]
  })),
  removeItem: (id) => set((state) => ({
    items: state.items.filter(item => item.id !== id)
  }))
}))

// 计算属性作为独立 selector
export const selectTotalPrice = (state: CartState) =>
  state.items.reduce((sum, item) => sum + item.price * item.quantity, 0)

export const selectItemCount = (state: CartState) =>
  state.items.reduce((sum, item) => sum + item.quantity, 0)

// 组件中使用
function CartSummary() {
  const totalPrice = useCartStore(selectTotalPrice)
  const itemCount = useCartStore(selectItemCount)
  
  return (
    <div>
      <p>Items: {itemCount}</p>
      <p>Total: ${totalPrice.toFixed(2)}</p>
    </div>
  )
}

5. Next.js App Router 集成(SSR)

TypeScript

javascript 复制代码
// stores/themeStore.ts
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'

interface ThemeState {
  theme: 'light' | 'dark'
  toggleTheme: () => void
}

export const useThemeStore = create<themestate>()(
  persist(
    (set) => ({
      theme: 'light',
      toggleTheme: () => set((state) => ({
        theme: state.theme === 'light' ? 'dark' : 'light'
      }))
    }),
    {
      name: 'theme-storage',
      storage: createJSONStorage(() => localStorage)
    }
  )
)

// app/providers.tsx - 客户端 Provider
'use client'

import { useEffect, useState } from 'react'
import { useThemeStore } from '@/stores/themeStore'

export function Providers({ children }) {
  const [mounted, setMounted] = useState(false)

  useEffect(() => {
    setMounted(true)
  }, [])

  if (!mounted) {
    return null  // 避免 SSR 水合不匹配
  }

  return <>{children}
}

中间件系统

1. 内置中间件

Persist - 状态持久化

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

const useStore = create(
  persist(
    (set) => ({
      user: null,
      setUser: (user) => set({ user })
    }),
    {
      name: 'user-storage',        // localStorage key
      storage: createJSONStorage(() => localStorage),
      partialize: (state) => ({ user: state.user }), // 部分持久化
      onRehydrateStorage: () => (state) => {
        console.log('Hydration finished', state)
      }
    }
  )
)

Devtools - Redux DevTools 集成

TypeScript

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

const useStore = create(
  devtools(
    (set) => ({
      count: 0,
      increment: () => set((state) => ({ count: state.count + 1 }), false, 'increment')
    }),
    { name: 'CounterStore' }
  )
)

Immer - 不可变更新简化

TypeScript 复制代码
import { immer } from 'zustand/middleware/immer'

const useStore = create(
  immer((set) => ({
    user: { name: 'John', address: { city: 'NYC' } },
    updateCity: (city) => set((state) => {
      state.user.address.city = city  // 直接修改,immer 处理不可变性
    })
  }))
)

2. 自定义中间件

日志中间件

TypeScript 复制代码
const logger = (config) => (set, get, api) =>
  config(
    (...args) => {
      console.log('  applying', args)
      set(...args)
      console.log('  new state', get())
    },
    get,
    api
  )

const useStore = create(logger((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 }))
})))

时间旅行中间件

TypeScript 复制代码
const timeTravel = (config) => (set, get, api) => {
  const history = []
  const maxHistory = 10

  return {
    ...config(
      (...args) => {
        history.push(get())
        if (history.length > maxHistory) history.shift()
        set(...args)
      },
      get,
      api
    ),
    undo: () => {
      if (history.length > 0) {
        const previous = history.pop()
        set(previous, true)
      }
    }
  }
}

请求去重中间件

TypeScript 复制代码
const dedupe = (config) => (set, get, api) => {
  const pending = new Map()

  return config(
    (partial, replace, action) => {
      if (typeof action === 'string' && pending.has(action)) {
        return pending.get(action)
      }

      const promise = Promise.resolve(set(partial, replace, action))
      
      if (typeof action === 'string') {
        pending.set(action, promise)
        promise.finally(() => pending.delete(action))
      }

      return promise
    },
    get,
    api
  )
}

性能优化

1. 选择器优化

TypeScript 复制代码
// ❌ 不好:每次渲染都创建新对象
function BadComponent() {
  const data = useStore((state) => ({
    user: state.user,
    posts: state.posts
  }))
  // 即使 user 和 posts 没变,对象引用变了,导致重渲染
}

// ✅ 好:使用 shallow 比较
import { shallow } from 'zustand/shallow'

function GoodComponent() {
  const { user, posts } = useStore(
    (state) => ({ user: state.user, posts: state.posts }),
    shallow
  )
}

// ✅ 更好:分别订阅
function BetterComponent() {
  const user = useStore((state) => state.user)
  const posts = useStore((state) => state.posts)
}

2. 避免不必要的重渲染

TypeScript 复制代码
// ❌ 不好:订阅整个 Store
function BadList() {
  const store = useStore()  // 任何状态变化都会重渲染
  return <div>{store.items.map(...)}</div>
}

// ✅ 好:只订阅需要的部分
function GoodList() {
  const items = useStore((state) => state.items)
  return <div>{items.map(...)}</div>
}

3. 使用 subscribeWithSelector

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

const useStore = create(
  subscribeWithSelector((set) => ({
    user: { name: 'John', age: 30 },
    posts: []
  }))
)

// 只在 user.name 变化时触发
useStore.subscribe(
  (state) => state.user.name,
  (name, prevName) => {
    console.log('Name changed:', prevName, '->', name)
  }
)

4. 大列表优化

TypeScript 复制代码
// stores/todoStore.ts
import { create } from 'zustand'

const useTodoStore = create((set) => ({
  todos: [],
  addTodo: (todo) => set((state) => ({
    todos: [...state.todos, todo]
  })),
  toggleTodo: (id) => set((state) => ({
    todos: state.todos.map(todo =>
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    )
  }))
}))

// 使用 React.memo 和选择器
const TodoItem = React.memo(({ id }) => {
  const todo = useTodoStore((state) =>
    state.todos.find(t => t.id === id)
  )
  const toggleTodo = useTodoStore((state) => state.toggleTodo)
  
  return (
    <div onclick="{()" ==""> toggleTodo(id)}>
      {todo.text}
    </div>
  )
})

function TodoList() {
  const todoIds = useTodoStore((state) => state.todos.map(t => t.id))
  return todoIds.map(id => <todoitem key="{id}" id="{id}">)
}

最佳实践

1. Store 组织结构

bash 复制代码
src/
├── stores/
│   ├── index.ts              # 导出所有 Store
│   ├── slices/               # Store 切片
│   │   ├── authSlice.ts
│   │   ├── userSlice.ts
│   │   └── cartSlice.ts
│   ├── middleware/           # 自定义中间件
│   │   ├── logger.ts
│   │   └── analytics.ts
│   └── selectors/            # 复用的选择器
│       ├── cartSelectors.ts
│       └── userSelectors.ts

2. TypeScript 类型安全

TypeScript 复制代码
// stores/types.ts
export interface User {
  id: string
  name: string
  email: string
}

export interface AuthState {
  user: User | null
  token: string | null
  isAuthenticated: boolean
}

export interface AuthActions {
  login: (email: string, password: string) => Promise<void>
  logout: () => void
  refreshToken: () => Promise<void>
}

// stores/authStore.ts
import { create } from 'zustand'
import type { AuthState, AuthActions } from './types'

type AuthStore = AuthState & AuthActions

export const useAuthStore = create<authstore>((set, get) => ({
  // State
  user: null,
  token: null,
  isAuthenticated: false,

  // Actions
  login: async (email, password) => {
    const response = await fetch('/api/login', {
      method: 'POST',
      body: JSON.stringify({ email, password })
    })
    const { user, token } = await response.json()
    set({ user, token, isAuthenticated: true })
  },

  logout: () => {
    set({ user: null, token: null, isAuthenticated: false })
  },

  refreshToken: async () => {
    const { token } = get()
    if (!token) return

    const response = await fetch('/api/refresh', {
      headers: { Authorization: `Bearer ${token}` }
    })
    const { token: newToken } = await response.json()
    set({ token: newToken })
  }
}))

3. 测试策略

TypeScript 复制代码
// stores/__tests__/counterStore.test.ts
import { renderHook, act } from '@testing-library/react'
import { useCounterStore } from '../counterStore'

describe('CounterStore', () => {
  beforeEach(() => {
    useCounterStore.setState({ count: 0 })
  })

  it('should increment count', () => {
    const { result } = renderHook(() => useCounterStore())
    
    act(() => {
      result.current.increment()
    })
    
    expect(result.current.count).toBe(1)
  })

  it('should reset count', () => {
    const { result } = renderHook(() => useCounterStore())
    
    act(() => {
      result.current.increment()
      result.current.increment()
      result.current.reset()
    })
    
    expect(result.current.count).toBe(0)
  })
})

4. 错误处理模式

TypeScript 复制代码
interface AsyncState<t> {
  data: T | null
  loading: boolean
  error: Error | null
}

const createAsyncSlice = <t>(
  fetcher: () => Promise<t>
) => ({
  ...initialAsyncState,
  fetch: async (set) => {
    set({ loading: true, error: null })
    try {
      const data = await fetcher()
      set({ data, loading: false })
    } catch (error) {
      set({ error, loading: false })
    }
  }
})

5. 性能监控

TypeScript 复制代码
const performanceMiddleware = (config) => (set, get, api) => {
  return config(
    (...args) => {
      const start = performance.now()
      set(...args)
      const end = performance.now()
      
      if (end - start > 16) { // 超过一帧时间
        console.warn(`Slow state update: ${end - start}ms`)
      }
    },
    get,
    api
  )
}

6. 开发环境调试

TypeScript 复制代码
// stores/devtools.ts
import { devtools } from 'zustand/middleware'

export const withDevtools = <t>(
  config: StateCreator<t>,
  name: string
) => {
  if (process.env.NODE_ENV === 'development') {
    return devtools(config, { name })
  }
  return config
}

// 使用
const useStore = create(
  withDevtools(
    (set) => ({ count: 0, increment: () => set((s) => ({ count: s.count + 1 })) }),
    'CounterStore'
  )
)

总结

Zustand 的核心价值

  1. 极简设计:43 行核心代码,1.2KB 体积
  2. 高性能 :基于 useSyncExternalStore,细粒度订阅
  3. 灵活扩展:强大的中间件系统
  4. 开发体验:TypeScript 友好,DevTools 支持

何时选择 Zustand

  • ✅ 中小型到大型应用
  • ✅ 需要全局状态但不想用 Redux
  • ✅ 性能敏感场景
  • ✅ 快速迭代项目

何时不选择 Zustand

  • ❌ 超大型企业应用(考虑 Redux + RTK)
  • ❌ 需要严格的状态管理规范(Redux 更合适)
  • ❌ 原子化状态需求(考虑 Jotai/Recoil)

学习资源


作者注:本文基于 Zustand v5.0 和 2025 年社区最佳实践编写,涵盖了从原理到实战的完整知识体系。希望能帮助你深入理解并高效使用 Zustand!

最后更新:2025年10月

相关推荐
fruge3 小时前
前端性能优化实战指南:从首屏加载到用户体验的全面提升
前端·性能优化·ux
ZYMFZ3 小时前
Redis主从复制与哨兵集群
前端·git·github
lumi.3 小时前
前端本地存储技术笔记:localStorage 与 sessionStorage 详解
前端·javascript·笔记
旧雨散尘3 小时前
【react】初学react5-react脚手架搭建中的小众知识
前端·react.js·前端框架
炫饭第一名3 小时前
🌍🌍🌍字节一面场景题:异步任务调度器
前端·javascript·面试
烛阴4 小时前
Lua字符串的利刃:模式匹配的艺术与实践
前端·lua
奇舞精选4 小时前
一文了解 Server-Sent Events (SSE):构建高效的服务器推送应用
前端
Yeats_Liao4 小时前
Go Web 编程快速入门 11 - WebSocket实时通信:实时消息推送和双向通信
前端·后端·websocket·golang
纯爱掌门人4 小时前
鸿蒙状态管理V2实战:从零构建MVVM架构的应用
前端·harmonyos