Skip to content

Zustand

本文介绍轻量级状态管理库Zustand,包括其核心特性、基本用法、中间件支持以及与React组件的集成方式。

简介

Zustand是一个轻量级的状态管理库,专为React应用设计,但也可以在非React环境中使用。它采用简单直观的API,解决了Redux等传统状态管理库的复杂性问题,同时保留了强大的功能和灵活性。

核心特性

  • 简单的API:没有样板代码,没有Provider,没有高阶组件
  • 轻量级:体积小(约3KB),性能高
  • 不依赖Context:避免了Context导致的不必要重渲染
  • 支持中间件:内置多种中间件,如持久化、日志记录等
  • TypeScript友好:完整的类型支持
  • 支持React之外的环境:可以在任何JavaScript环境中使用

安装

bash
# 使用npm
npm install zustand

# 使用yarn
yarn add zustand

# 使用pnpm
pnpm add zustand

基本用法

创建Store

使用create函数创建一个store:

jsx
import { create } from 'zustand'

// 定义store
const useStore = create((set) => ({
  // 状态
  bears: 0,
  // 更新状态的方法
  increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
  removeAllBears: () => set({ bears: 0 }),
}))

在组件中使用

jsx
import React from 'react'
import { useStore } from './store' // 假设上面的store代码保存在store.js文件中

function BearCounter() {
  // 从store中提取状态
  const bears = useStore((state) => state.bears)
  return <h1>{bears} bears around here...</h1>
}

function Controls() {
  // 从store中提取方法
  const increasePopulation = useStore((state) => state.increasePopulation)
  const removeAllBears = useStore((state) => state.removeAllBears)
  
  return (
    <div>
      <button onClick={increasePopulation}>增加熊的数量</button>
      <button onClick={removeAllBears}>移除所有熊</button>
    </div>
  )
}

function App() {
  return (
    <>
      <BearCounter />
      <Controls />
    </>
  )
}

异步操作

Zustand可以轻松处理异步操作:

jsx
import { create } from 'zustand'

const useStore = create((set) => ({
  fishes: 0,
  loading: false,
  error: null,
  // 异步增加鱼的数量
  fetchFishes: async () => {
    try {
      set({ loading: true, error: null })
      // 模拟API调用
      const response = await fetch('https://api.example.com/fishes')
      const data = await response.json()
      set({ fishes: data.count, loading: false })
    } catch (error) {
      set({ error: error.message, loading: false })
    }
  },
}))

使用TypeScript

Zustand对TypeScript有很好的支持:

typescript
import { create } from 'zustand'

// 定义状态类型
interface BearState {
  bears: number
  increasePopulation: () => void
  removeAllBears: () => void
}

// 创建有类型的store
const useStore = create<BearState>((set) => ({
  bears: 0,
  increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
  removeAllBears: () => set({ bears: 0 }),
}))

高级用法

状态选择器

使用选择器可以优化性能,只在相关状态变化时重新渲染组件:

jsx
function BearCounter() {
  // 只有bears变化时才会重新渲染
  const bears = useStore((state) => state.bears)
  return <h1>{bears} bears around here...</h1>
}

状态更新

Zustand提供了多种更新状态的方式:

jsx
const useStore = create((set) => ({
  // 状态
  count: 0,
  text: 'hello',
  
  // 1. 直接替换状态
  setCount: (newCount) => set({ count: newCount }),
  
  // 2. 基于当前状态更新
  increment: () => set((state) => ({ count: state.count + 1 })),
  
  // 3. 部分更新状态(保留其他字段)
  updateText: (newText) => set({ text: newText }),
  
  // 4. 完全替换状态(不保留其他字段)
  reset: () => set({ count: 0, text: '' }, true),
}))

读取状态(不在组件中)

可以在组件外部读取状态:

jsx
const { getState, setState } = useStore

// 读取当前状态
const bears = getState().bears

// 更新状态
setState({ bears: bears + 1 })

多个Store

可以创建多个独立的store:

jsx
import { create } from 'zustand'

// 用户store
const useUserStore = create((set) => ({
  user: null,
  loading: false,
  login: async (credentials) => {
    set({ loading: true })
    // 登录逻辑
    // ...
  },
  logout: () => set({ user: null }),
}))

// 购物车store
const useCartStore = create((set) => ({
  items: [],
  addItem: (item) => set((state) => ({ items: [...state.items, item] })),
  removeItem: (id) => set((state) => ({
    items: state.items.filter(item => item.id !== id)
  })),
  clearCart: () => set({ items: [] }),
}))

中间件

Zustand支持中间件,可以扩展store的功能。

持久化中间件

使用persist中间件将状态保存到localStorage或其他存储中:

jsx
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'

const useStore = create(
  persist(
    (set) => ({
      bears: 0,
      increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
      removeAllBears: () => set({ bears: 0 }),
    }),
    {
      name: 'bear-storage', // 存储的唯一名称
      storage: createJSONStorage(() => localStorage), // 使用localStorage
      // 可选:只持久化某些状态
      partialize: (state) => ({ bears: state.bears }),
    }
  )
)

日志中间件

使用devtools中间件可以在Redux DevTools中查看状态变化:

jsx
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'

const useStore = create(
  devtools(
    (set) => ({
      bears: 0,
      increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
      removeAllBears: () => set({ bears: 0 }),
    })
  )
)

组合中间件

可以组合多个中间件:

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

const useStore = create(
  devtools(
    persist(
      (set) => ({
        bears: 0,
        increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
        removeAllBears: () => set({ bears: 0 }),
      }),
      {
        name: 'bear-storage',
      }
    )
  )
)

自定义中间件

可以创建自定义中间件:

jsx
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) => ({
    bears: 0,
    increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
  }))
)

与React集成

使用Context优化

对于大型应用,可以使用Context来避免prop drilling:

jsx
import { create } from 'zustand'
import { createContext, useContext } from 'react'

// 创建context
const StoreContext = createContext()

// 创建store hook
const createStore = () =>
  create((set) => ({
    bears: 0,
    increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
    removeAllBears: () => set({ bears: 0 }),
  }))

// 提供store的Provider组件
export const StoreProvider = ({ children }) => {
  const storeRef = useRef()
  if (!storeRef.current) {
    storeRef.current = createStore()
  }
  return (
    <StoreContext.Provider value={storeRef.current}>
      {children}
    </StoreContext.Provider>
  )
}

// 自定义hook使用store
export const useStore = (selector) => {
  const store = useContext(StoreContext)
  return store(selector)
}

与React Query结合

Zustand可以与React Query等数据获取库结合使用:

jsx
import { create } from 'zustand'
import { useQuery } from 'react-query'

// 创建store
const useStore = create((set) => ({
  selectedUserId: null,
  setSelectedUserId: (id) => set({ selectedUserId: id }),
}))

// 在组件中使用
function UserDetails() {
  const selectedUserId = useStore((state) => state.selectedUserId)
  
  // 使用React Query获取数据
  const { data, isLoading, error } = useQuery(
    ['user', selectedUserId],
    () => fetch(`/api/users/${selectedUserId}`).then(res => res.json()),
    { enabled: !!selectedUserId }
  )
  
  if (!selectedUserId) return <p>请选择一个用户</p>
  if (isLoading) return <p>加载中...</p>
  if (error) return <p>错误: {error.message}</p>
  
  return (
    <div>
      <h2>{data.name}</h2>
      <p>{data.email}</p>
    </div>
  )
}

性能优化

避免不必要的重渲染

使用选择器可以避免不必要的重渲染:

jsx
// 不好的做法:获取整个状态对象
const state = useStore()
const { bears, fishes } = state // 任何状态变化都会导致重渲染

// 好的做法:只选择需要的状态
const bears = useStore((state) => state.bears) // 只有bears变化时才重渲染
const fishes = useStore((state) => state.fishes) // 只有fishes变化时才重渲染

使用shallow比较

对于对象或数组状态,使用shallow比较可以避免不必要的重渲染:

jsx
import { create } from 'zustand'
import { shallow } from 'zustand/shallow'

function Component() {
  // 使用shallow比较,只有当数组中的元素实际变化时才重渲染
  const [bears, fishes] = useStore(
    (state) => [state.bears, state.fishes],
    shallow
  )
  
  // 对于对象也可以使用shallow
  const { bears, fishes } = useStore(
    (state) => ({ bears: state.bears, fishes: state.fishes }),
    shallow
  )
  
  return (
    <div>
      <p>Bears: {bears}</p>
      <p>Fishes: {fishes}</p>
    </div>
  )
}

使用useShallow

Zustand提供了useShallow钩子简化shallow比较:

jsx
import { useShallow } from 'zustand/react/shallow'

function Component() {
  const { bears, fishes } = useStore(
    useShallow((state) => ({ bears: state.bears, fishes: state.fishes }))
  )
  
  return (
    <div>
      <p>Bears: {bears}</p>
      <p>Fishes: {fishes}</p>
    </div>
  )
}

最佳实践

组织Store

对于大型应用,可以将store分割成多个slice:

jsx
import { create } from 'zustand'

// 创建用户slice
const createUserSlice = (set) => ({
  user: null,
  loading: false,
  error: null,
  login: async (credentials) => {
    try {
      set({ loading: true, error: null })
      // 登录逻辑
      const user = await loginApi(credentials)
      set({ user, loading: false })
    } catch (error) {
      set({ error: error.message, loading: false })
    }
  },
  logout: () => set({ user: null }),
})

// 创建购物车slice
const createCartSlice = (set, get) => ({
  items: [],
  totalItems: 0,
  totalPrice: 0,
  addItem: (item) => {
    set((state) => {
      const newItems = [...state.items, item]
      return {
        items: newItems,
        totalItems: newItems.length,
        totalPrice: newItems.reduce((sum, item) => sum + item.price, 0),
      }
    })
  },
  removeItem: (id) => {
    set((state) => {
      const newItems = state.items.filter(item => item.id !== id)
      return {
        items: newItems,
        totalItems: newItems.length,
        totalPrice: newItems.reduce((sum, item) => sum + item.price, 0),
      }
    })
  },
  clearCart: () => set({ items: [], totalItems: 0, totalPrice: 0 }),
})

// 组合所有slice创建store
const useStore = create((set, get) => ({
  ...createUserSlice(set, get),
  ...createCartSlice(set, get),
}))

使用Immer简化状态更新

使用Immer可以简化复杂状态的更新:

jsx
import { create } from 'zustand'
import { immer } from 'zustand/middleware/immer'

const useStore = create(
  immer((set) => ({
    nested: {
      structure: {
        count: 0,
      },
    },
    // 使用Immer可以直接修改状态
    increment: () => set((state) => {
      state.nested.structure.count += 1
    }),
    addTodo: (todo) => set((state) => {
      state.todos.push(todo)
    }),
  }))
)

测试

测试Zustand store:

jsx
import { create } from 'zustand'
import { renderHook, act } from '@testing-library/react-hooks'

// 创建store
const createStore = () =>
  create((set) => ({
    bears: 0,
    increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
    removeAllBears: () => set({ bears: 0 }),
  }))

describe('Bear store', () => {
  it('should increase population', () => {
    const { result } = renderHook(() => createStore())
    
    act(() => {
      result.current.getState().increasePopulation()
    })
    
    expect(result.current.getState().bears).toBe(1)
  })
  
  it('should remove all bears', () => {
    const { result } = renderHook(() => createStore())
    
    act(() => {
      result.current.getState().increasePopulation()
      result.current.getState().increasePopulation()
      result.current.getState().removeAllBears()
    })
    
    expect(result.current.getState().bears).toBe(0)
  })
})

与其他状态管理库对比

Zustand vs Redux

特性ZustandRedux
体积轻量级 (~3KB)较大
样板代码极少较多
学习曲线简单陡峭
中间件内置支持内置支持
DevTools支持支持
性能优秀优秀
TypeScript完全支持完全支持
社区生态成长中成熟

Zustand vs Context API

特性ZustandContext API
性能优秀,避免不必要的重渲染可能导致不必要的重渲染
复杂度中等
状态更新简单直观需要自行实现
中间件内置支持不支持
持久化内置支持需要自行实现
全局访问支持需要Provider包裹

总结

Zustand是一个简单而强大的状态管理库,它提供了:

  • 简洁的API,减少样板代码
  • 出色的性能和灵活性
  • 丰富的中间件支持
  • 与React的无缝集成
  • TypeScript的完全支持

对于中小型React应用,Zustand是一个理想的状态管理解决方案,它平衡了简单性和功能性,让开发者可以专注于业务逻辑而不是状态管理的复杂性。