React中类似于Vue中Pinia的轻量级状态管理神器——Zustand

Zustand ​ 是一个轻量级、简洁的React状态管理库,核心特点是无样板代码、hooks风格、不依赖Context。用法很像Vue生态中的Pinia,以下是详细使用指南:

一、基础使用(计数器示例)

1. 创建store

typescript 复制代码
// store/counterStore.ts
import { create } from 'zustand'

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

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 }),
}))

2. 组件中使用

javascript 复制代码
// Counter.tsx
import useCounterStore from './store/counterStore'

const Counter = () => {
  // 自动订阅状态变化 - 当count变化时组件重渲染
  const { count, increment, decrement } = useCounterStore()
  
  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </div>
  )
}

// 兄弟组件同步更新
const Display = () => {
  const count = useCounterStore((state) => state.count) // 选择性订阅
  
  return <div>Current count: {count}</div>
}

二、进阶特性

1. 异步操作

typescript 复制代码
// store/userStore.ts
interface UserState {
  user: User | null
  loading: boolean
  fetchUser: (id: string) => Promise<void>
}

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

2. 深层嵌套状态更新

typescript 复制代码
// 使用immer简化嵌套更新(需安装immer)
import { produce } from 'immer'

const useTodoStore = create<{
  todos: Todo[]
  addTodo: (text: string) => void
  toggleTodo: (id: number) => void
}>((set) => ({
  todos: [],
  addTodo: (text) => set(produce((state) => {
    state.todos.push({ id: Date.now(), text, completed: false })
  })),
  toggleTodo: (id) => set(produce((state) => {
    const todo = state.todos.find(t => t.id === id)
    if (todo) todo.completed = !todo.completed
  })),
}))

3. 性能优化:选择性订阅

javascript 复制代码
// ✅ 只订阅需要的状态,避免不必要的重渲染
const TodoList = () => {
  const todos = useTodoStore((state) => state.todos) // 仅当todos变化时重渲染
  
  const addTodo = useTodoStore((state) => state.addTodo) // action不会触发重渲染
  
  return (
    <div>
      {todos.map(todo => <TodoItem key={todo.id} />)}
      <AddTodo onAdd={addTodo} />
    </div>
  )
}

// TodoItem组件 - 独立订阅
const TodoItem = ({ id }) => {
  const todo = useTodoStore(
    (state) => state.todos.find(t => t.id === id)
  )
  const toggleTodo = useTodoStore((state) => state.toggleTodo)
  
  // 每个TodoItem只在自己的todo变化时重渲染
  return <li onClick={() => toggleTodo(id)}>{todo.text}</li>
}

4. 中间件使用

javascript 复制代码
// 持久化存储(需安装zustand/middleware)
import { persist, createJSONStorage } from 'zustand/middleware'

const useAuthStore = create(
  persist(
    (set, get) => ({
      token: null,
      user: null,
      login: (credentials) => { /* ... */ },
      logout: () => set({ token: null, user: null }),
    }),
    {
      name: 'auth-storage', // localStorage key
      storage: createJSONStorage(() => localStorage),
    }
  )
)

// 开发工具中间件
import { devtools } from 'zustand/middleware'

const useStore = create(
  devtools(
    (set) => ({
      /* state & actions */
    }),
    { name: 'MyStore' } // Redux DevTools中的显示名称
  )
)

三、最佳实践

1. 拆分store(按领域划分)

bash 复制代码
store/
├── authStore.ts      # 认证相关
├── cartStore.ts      # 购物车
├── uiStore.ts        # UI状态(主题、弹窗)
└── productStore.ts   # 商品数据

2. TypeScript完整示例

typescript 复制代码
// store/authStore.ts
interface User {
  id: string
  name: string
  email: string
}

interface AuthState {
  user: User | null
  isAuthenticated: boolean
  login: (email: string, password: string) => Promise<void>
  logout: () => void
  initialize: () => Promise<void>
}

export const useAuthStore = create<AuthState>((set, get) => ({
  user: null,
  isAuthenticated: false,
  
  login: async (email: string, password: string) => {
    const response = await fetch('/api/login', {
      method: 'POST',
      body: JSON.stringify({ email, password }),
    })
    
    const user = await response.json()
    set({ user, isAuthenticated: true })
  },
  
  logout: () => {
    localStorage.removeItem('token')
    set({ user: null, isAuthenticated: false })
  },
  
  initialize: async () => {
    const token = localStorage.getItem('token')
    if (token) {
      // 验证token并获取用户信息
      const user = await fetchCurrentUser()
      set({ user, isAuthenticated: true })
    }
  },
}))

3. 在类组件中使用(兼容方案)

javascript 复制代码
// withStore HOC
import { useCounterStore } from './store/counterStore'

const withCounterStore = (Component) => {
  return (props) => {
    const store = useCounterStore()
    return <Component {...props} store={store} />
  }
}

class LegacyComponent extends React.Component {
  render() {
    const { store } = this.props
    return <div>Count: {store.count}</div>
  }
}

export default withCounterStore(LegacyComponent)

四、对比其他方案

特性 Zustand Redux Toolkit Context
包大小 ~1KB ~8KB 内置
学习曲线 极低 中等
样板代码 极少 中等
性能 自动优化 手动优化 需要Memo
DevTools 插件支持 内置完整
异步 原生支持 RTK Query 需手动处理

五、与Pinia的开发体验对比

方面 Zustand Pinia 评价
学习成本 极低 Zustand更简单
代码简洁度 极高 Zustand更函数式
Vue集成 不适用 完美 Pinia是Vue生态一部分
React集成 原生hooks 不适用 Zustand是React专用
调试体验 需要中间件 开箱即用 Pinia胜出
社区生态 增长快 Vue官方 Pinia更稳定

六、快速开始

bash 复制代码
# 安装
npm install zustand

# 可选中间件
npm install immer @types/immer  # 不可变更新
npm install zustand/middleware  # 官方中间件

总结 :Zustand通过极简的API提供了完整的全局状态管理能力,适合大多数React项目。其核心优势是零样板、直观、高性能,避免了Redux的复杂性和Context的性能陷阱。

相关推荐
于慨1 天前
Lambda 表达式、方法引用(Method Reference)语法
java·前端·servlet
石小石Orz1 天前
油猴脚本实现生产环境加载本地qiankun子应用
前端·架构
从前慢丶1 天前
前端交互规范(Web 端)
前端
CHU7290351 天前
便捷约玩,沉浸推理:线上剧本杀APP功能版块设计详解
前端·小程序
GISer_Jing1 天前
Page-agent MCP结构
前端·人工智能
王霸天1 天前
💥别再抄网上的Scale缩放代码了!50行源码教你写一个永不翻车的大屏适配
前端·vue.js·数据可视化
小领航1 天前
用 Three.js + Vue 3 打造炫酷的 3D 行政地图可视化组件
前端·github
@大迁世界1 天前
2026年React大洗牌:React Hooks 将迎来重大升级
前端·javascript·react.js·前端框架·ecmascript
PieroPc1 天前
一个功能强大的 Web 端标签设计和打印工具,支持服务器端直接打印到局域网打印机。Fastapi + html
前端·html·fastapi
悟空瞎说1 天前
深入 Vue3 响应式:为什么有的要加.value,有的不用?从设计到源码彻底讲透
前端·vue.js