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

6 阅读3分钟

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

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

1. 创建store

// 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. 组件中使用

// 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. 异步操作

// 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. 深层嵌套状态更新

// 使用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. 性能优化:选择性订阅

// ✅ 只订阅需要的状态,避免不必要的重渲染
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. 中间件使用

// 持久化存储(需安装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(按领域划分)

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

2. 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. 在类组件中使用(兼容方案)

// 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)

四、对比其他方案

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

五、与Pinia的开发体验对比

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

六、快速开始

# 安装
npm install zustand

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

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