Skip to content

Zustand 状态管理深度解析:比 Redux 更简洁的现代状态管理方案 ​

在 React 状态管理的世界里,Zustand 以其极简的 API 和强大的能力脱颖而出。本文将深入探讨 Zustand 的核心原理、实战技巧与最佳实践。

为什么选择 Zustand? ​

在 React 生态中,状态管理一直是热门话题。Redux 过于繁琐,Context API 性能堪忧,而 Zustand 完美平衡了 simplicity 和 power:

  • 零样板代码 — 无需 actions、reducers、dispatchers
  • 极小体积 — 压缩后仅约 1KB
  • 自动订阅 — 组件按需渲染,避免不必要的 re-render
  • TypeScript 友好 — 完美的类型推断
  • 中间件支持 — 强大的扩展能力

核心实现原理 ​

1. 基础 Store 创建 ​

typescript
import { create } from 'zustand'

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

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

2. 原理剖析:create 函数如何工作 ​

Zustand 的核心在于 Store 创建函数 和 订阅机制:

typescript
// Zustand 核心实现简化版
function create<T>(initializer: (set: SetState<T>) => T) {
  // 1. 创建全局 store
  const store = { state: initializer(set) }

  // 2. 订阅者集合
  const subscribers = new Set<() => void>()

  // 3. set 函数实现
  const set: SetState<T> = (partial) => {
    store.state = typeof partial === 'function'
      ? partial(store.state)
      : partial
    // 通知所有订阅者
    subscribers.forEach(fn => fn())
  }

  // 4. 返回 hooks
  return <U>(selector: (state: T) => U) => {
    const [state, setState] = useState(() => selector(store.state))

    useEffect(() => {
      const listener = () => {
        const newState = selector(store.state)
        if (newState !== state) {
          setState(newState)
        }
      }
      subscribers.add(listener)
      return () => subscribers.delete(listener)
    }, [selector])

    return state
  }
}

关键点:

  • 全局单例 — store 是唯一的,不依赖组件树
  • 选择器订阅 — 组件只订阅关心的 state 片段
  • 浅比较 — 使用 Object.is 做比较,避免对象引用导致的过度渲染

3. 订阅机制的深入理解 ​

typescript
// 高级订阅模式
const useStore = create((set) => ({
  user: null,
  isLoading: false,
  setUser: (user) => set({ user }),
}))

// 方式1: 选择器订阅(推荐)
function UserProfile() {
  // 只订阅 user 变化,不关心 isLoading
  const user = useStore((state) => state.user)
  return <div>{user?.name}</div>
}

// 方式2: 订阅整个 store
function App() {
  const wholeStore = useStore() // 每次 store 变化都触发
  return <div>{wholeStore.user?.name}</div>
}

// 方式3: 订阅多个选择器
function Dashboard() {
  const user = useStore((s) => s.user)
  const isLoading = useStore((s) => s.isLoading)
  // ...
}

实战技巧 ​

1. 派生状态(Derived State) ​

typescript
const useStore = create((set, get) => ({
  items: [],
  filter: 'all',

  // 不需要单独存储 computed 值
  // 直接在组件中计算,或使用 selector
  // items 变化时 computed 自动更新
}))

// 组件中使用
function FilteredList() {
  const items = useStore((s) => s.items)
  const filter = useStore((s) => s.filter)

  const filteredItems = useMemo(() => {
    if (filter === 'all') return items
    return items.filter(i => i.status === filter)
  }, [items, filter])

  return <List items={filteredItems} />
}

2. 异步 Actions ​

typescript
interface UserStore {
  users: User[]
  loading: boolean
  error: string | null
  fetchUsers: () => Promise<void>
}

const useUserStore = create<UserStore>((set) => ({
  users: [],
  loading: false,
  error: null,

  fetchUsers: async () => {
    set({ loading: true, error: null })
    try {
      const response = await fetch('/api/users')
      const users = await response.json()
      set({ users, loading: false })
    } catch (error) {
      set({ error: error.message, loading: false })
    }
  },
}))

3. 中间件系统 ​

typescript
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'

// 日志中间件
const logger = (config) => (set, get, api) => {
  console.log('[Store]', get())
  return config(set, get, api)
}

// 持久化中间件
const useStore = create(
  devtools(
    persist(
      (set) => ({
        theme: 'dark',
        setTheme: (theme) => set({ theme }),
      }),
      {
        name: 'theme-storage', // localStorage key
        partialize: (state) => ({ theme: state.theme }), // 只持久化部分
      }
    ),
    { name: 'theme-store' } // DevTools name
  )
)

4. 状态切片模式(Slice Pattern) ​

大型应用推荐将 store 拆分为多个 slice:

typescript
// userSlice.ts
interface UserSlice {
  user: User | null
  setUser: (user: User) => void
  logout: () => void
}

const createUserSlice = (set) => ({
  user: null,
  setUser: (user) => set({ user }),
  logout: () => set({ user: null }),
})

// cartSlice.ts
interface CartSlice {
  cart: CartItem[]
  addToCart: (item: CartItem) => void
  removeFromCart: (id: string) => void
}

const createCartSlice = (set) => ({
  cart: [],
  addToCart: (item) => set((state) => ({
    cart: [...state.cart, item]
  })),
  removeFromCart: (id) => set((state) => ({
    cart: state.cart.filter(i => i.id !== id)
  })),
})

// 组合 slice
type Store = UserSlice & CartSlice

const useStore = create<Store>()((...args) => ({
  ...createUserSlice(...args),
  ...createCartSlice(...args),
}))

性能优化实战 ​

1. 避免不必要的重新渲染 ​

typescript
// 错误 错误: 每次 store 变化都重新渲染
function BadComponent() {
  const { count, increment } = useStore() // 订阅整个 store
  return <button onClick={increment}>{count}</button>
}

// 正确 正确: 只订阅需要的部分
function GoodComponent() {
  const count = useStore((s) => s.count)
  const increment = useStore((s) => s.increment)
  return <button onClick={increment}>{count}</button>
}

// 正确 更好的方式: 使用 shallow 比较
import { shallow } from 'zustand/shallow'

function BestComponent() {
  const { count, name } = useStore(
    (state) => ({ count: state.count, name: state.name }),
    shallow // 浅比较,避免对象引用变化触发重渲染
  )
  return <div>{name}: {count}</div>
}

2. 选择器优化技巧 ​

typescript
// 当需要访问多个 state 片段时
function UserCard() {
  // 错误 每次渲染创建新对象
  const userData = useStore((s) => ({
    user: s.user,
    isOnline: s.isOnline
  }))

  // 正确 使用 shallow 或分开的 selector
  const user = useStore((s) => s.user)
  const isOnline = useStore((s) => s.isOnline)
}

3. 重置状态 ​

typescript
const useStore = create((set) => ({
  count: 0,
  // 重置到初始状态
  reset: () => set({ count: 0 }),

  // 重置整个 store
  $reset: () => set(initialState, true), // 第二个参数 true 表示重置
}))

与其他方案对比 ​

特性ZustandRedux ToolkitContext API
体积~1KB~10KB内置
API 复杂度极简中等简单
性能优秀优秀一般
学习曲线低中低
TypeScript完美优秀一般
DevTools支持优秀无
中间件支持支持无

最佳实践总结 ​

  1. 保持 store 扁平 — 嵌套越浅越好管理
  2. 使用 TypeScript — 充分利用类型推断
  3. 选择器精确 — 只订阅需要的字段
  4. 合理拆分 — 大项目用 slice pattern
  5. 善用中间件 — devtools、persist、subscribeWithSelector
  6. 避免派生状态存储 — 用 selector 即时计算

Zustand 用它的理念证明了一个道理:好的工具不需要复杂,复杂的工具不一定好。在这个追求极简的时代,Zustand 无疑是 React 状态管理的绝佳选择。


本文由小虾子 撰写

Last updated: