zustand-advanced-patterns

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Zustand - Advanced Patterns

Zustand - 高级模式

Advanced techniques and patterns for building complex applications with Zustand, including transient updates, optimistic updates, and sophisticated state management strategies.
使用Zustand构建复杂应用的高级技巧与模式,包括瞬时更新、乐观更新以及复杂状态管理策略。

Key Concepts

核心概念

Transient Updates

瞬时更新

Update state without triggering re-renders:
typescript
const useStore = create((set) => ({
  count: 0,
  increment: () =>
    set((state) => ({ count: state.count + 1 }), false, 'increment'),
}))

// Usage: Update without re-rendering
useStore.setState({ count: 10 }, true) // replace: true, skip re-render
在不触发重新渲染的情况下更新状态:
typescript
const useStore = create((set) => ({
  count: 0,
  increment: () =>
    set((state) => ({ count: state.count + 1 }), false, 'increment'),
}))

// 用法:更新但不触发重新渲染
useStore.setState({ count: 10 }, true) // replace: true,跳过重新渲染

Subscriptions with Selectors

带选择器的订阅

Subscribe to specific slices of state:
typescript
const useStore = create<Store>()((set) => ({ /* ... */ }))

// Subscribe only to count changes
const unsubscribe = useStore.subscribe(
  (state) => state.count,
  (count, prevCount) => {
    console.log(`Count changed from ${prevCount} to ${count}`)
  },
  {
    equalityFn: (a, b) => a === b,
    fireImmediately: false,
  }
)
订阅状态的特定片段:
typescript
const useStore = create<Store>()((set) => ({ /* ... */ }))

// 仅订阅count的变化
const unsubscribe = useStore.subscribe(
  (state) => state.count,
  (count, prevCount) => {
    console.log(`Count changed from ${prevCount} to ${count}`)
  },
  {
    equalityFn: (a, b) => a === b,
    fireImmediately: false,
  }
)

Best Practices

最佳实践

1. Optimistic Updates

1. 乐观更新

Update UI immediately, then sync with server:
typescript
interface TodoStore {
  todos: Todo[]
  addTodo: (text: string) => Promise<void>
  updateTodo: (id: string, text: string) => Promise<void>
  deleteTodo: (id: string) => Promise<void>
}

const useTodoStore = create<TodoStore>()((set, get) => ({
  todos: [],

  addTodo: async (text) => {
    const optimisticTodo = {
      id: `temp-${Date.now()}`,
      text,
      completed: false,
    }

    // Optimistic update
    set((state) => ({
      todos: [...state.todos, optimisticTodo],
    }))

    try {
      const savedTodo = await api.createTodo({ text })

      // Replace optimistic todo with real one
      set((state) => ({
        todos: state.todos.map((todo) =>
          todo.id === optimisticTodo.id ? savedTodo : todo
        ),
      }))
    } catch (error) {
      // Rollback on error
      set((state) => ({
        todos: state.todos.filter((todo) => todo.id !== optimisticTodo.id),
      }))
      throw error
    }
  },

  updateTodo: async (id, text) => {
    const previousTodos = get().todos

    // Optimistic update
    set((state) => ({
      todos: state.todos.map((todo) =>
        todo.id === id ? { ...todo, text } : todo
      ),
    }))

    try {
      await api.updateTodo(id, { text })
    } catch (error) {
      // Rollback on error
      set({ todos: previousTodos })
      throw error
    }
  },

  deleteTodo: async (id) => {
    const previousTodos = get().todos

    // Optimistic update
    set((state) => ({
      todos: state.todos.filter((todo) => todo.id !== id),
    }))

    try {
      await api.deleteTodo(id)
    } catch (error) {
      // Rollback on error
      set({ todos: previousTodos })
      throw error
    }
  },
}))
立即更新UI,随后与服务器同步:
typescript
interface TodoStore {
  todos: Todo[]
  addTodo: (text: string) => Promise<void>
  updateTodo: (id: string, text: string) => Promise<void>
  deleteTodo: (id: string) => Promise<void>
}

const useTodoStore = create<TodoStore>()((set, get) => ({
  todos: [],

  addTodo: async (text) => {
    const optimisticTodo = {
      id: `temp-${Date.now()}`,
      text,
      completed: false,
    }

    // 乐观更新
    set((state) => ({
      todos: [...state.todos, optimisticTodo],
    }))

    try {
      const savedTodo = await api.createTodo({ text })

      // 用真实数据替换乐观更新的todo
      set((state) => ({
        todos: state.todos.map((todo) =>
          todo.id === optimisticTodo.id ? savedTodo : todo
        ),
      }))
    } catch (error) {
      // 出错时回滚
      set((state) => ({
        todos: state.todos.filter((todo) => todo.id !== optimisticTodo.id),
      }))
      throw error
    }
  },

  updateTodo: async (id, text) => {
    const previousTodos = get().todos

    // 乐观更新
    set((state) => ({
      todos: state.todos.map((todo) =>
        todo.id === id ? { ...todo, text } : todo
      ),
    }))

    try {
      await api.updateTodo(id, { text })
    } catch (error) {
      // 出错时回滚
      set({ todos: previousTodos })
      throw error
    }
  },

  deleteTodo: async (id) => {
    const previousTodos = get().todos

    // 乐观更新
    set((state) => ({
      todos: state.todos.filter((todo) => todo.id !== id),
    }))

    try {
      await api.deleteTodo(id)
    } catch (error) {
      // 出错时回滚
      set({ todos: previousTodos })
      throw error
    }
  },
}))

2. Undo/Redo Pattern

2. 撤销/重做模式

Implement time-travel functionality:
typescript
interface HistoryState<T> {
  past: T[]
  present: T
  future: T[]
}

interface HistoryStore<T> {
  history: HistoryState<T>
  canUndo: boolean
  canRedo: boolean
  set: (newPresent: T) => void
  undo: () => void
  redo: () => void
  reset: (initialState: T) => void
}

function createHistoryStore<T>(initialState: T) {
  return create<HistoryStore<T>>()((set, get) => ({
    history: {
      past: [],
      present: initialState,
      future: [],
    },

    get canUndo() {
      return get().history.past.length > 0
    },

    get canRedo() {
      return get().history.future.length > 0
    },

    set: (newPresent) =>
      set((state) => ({
        history: {
          past: [...state.history.past, state.history.present],
          present: newPresent,
          future: [],
        },
      })),

    undo: () =>
      set((state) => {
        if (state.history.past.length === 0) return state

        const previous = state.history.past[state.history.past.length - 1]
        const newPast = state.history.past.slice(0, -1)

        return {
          history: {
            past: newPast,
            present: previous,
            future: [state.history.present, ...state.history.future],
          },
        }
      }),

    redo: () =>
      set((state) => {
        if (state.history.future.length === 0) return state

        const next = state.history.future[0]
        const newFuture = state.history.future.slice(1)

        return {
          history: {
            past: [...state.history.past, state.history.present],
            present: next,
            future: newFuture,
          },
        }
      }),

    reset: (initialState) =>
      set({
        history: {
          past: [],
          present: initialState,
          future: [],
        },
      }),
  }))
}

// Usage
interface CanvasState {
  shapes: Shape[]
  selectedId: string | null
}

const useCanvasStore = createHistoryStore<CanvasState>({
  shapes: [],
  selectedId: null,
})

function Canvas() {
  const { present } = useCanvasStore((state) => state.history)
  const { canUndo, canRedo, undo, redo } = useCanvasStore()

  return (
    <div>
      <button onClick={undo} disabled={!canUndo}>
        Undo
      </button>
      <button onClick={redo} disabled={!canRedo}>
        Redo
      </button>
      {/* Render canvas */}
    </div>
  )
}
实现时间回溯功能:
typescript
interface HistoryState<T> {
  past: T[]
  present: T
  future: T[]
}

interface HistoryStore<T> {
  history: HistoryState<T>
  canUndo: boolean
  canRedo: boolean
  set: (newPresent: T) => void
  undo: () => void
  redo: () => void
  reset: (initialState: T) => void
}

function createHistoryStore<T>(initialState: T) {
  return create<HistoryStore<T>>()((set, get) => ({
    history: {
      past: [],
      present: initialState,
      future: [],
    },

    get canUndo() {
      return get().history.past.length > 0
    },

    get canRedo() {
      return get().history.future.length > 0
    },

    set: (newPresent) =>
      set((state) => ({
        history: {
          past: [...state.history.past, state.history.present],
          present: newPresent,
          future: [],
        },
      })),

    undo: () =>
      set((state) => {
        if (state.history.past.length === 0) return state

        const previous = state.history.past[state.history.past.length - 1]
        const newPast = state.history.past.slice(0, -1)

        return {
          history: {
            past: newPast,
            present: previous,
            future: [state.history.present, ...state.history.future],
          },
        }
      }),

    redo: () =>
      set((state) => {
        if (state.history.future.length === 0) return state

        const next = state.history.future[0]
        const newFuture = state.history.future.slice(1)

        return {
          history: {
            past: [...state.history.past, state.history.present],
            present: next,
            future: newFuture,
          },
        }
      }),

    reset: (initialState) =>
      set({
        history: {
          past: [],
          present: initialState,
          future: [],
        },
      }),
  }))
}

// 用法
interface CanvasState {
  shapes: Shape[]
  selectedId: string | null
}

const useCanvasStore = createHistoryStore<CanvasState>({
  shapes: [],
  selectedId: null,
})

function Canvas() {
  const { present } = useCanvasStore((state) => state.history)
  const { canUndo, canRedo, undo, redo } = useCanvasStore()

  return (
    <div>
      <button onClick={undo} disabled={!canUndo}>
        撤销
      </button>
      <button onClick={redo} disabled={!canRedo}>
        重做
      </button>
      {/* 渲染画布 */}
    </div>
  )
}

3. Store Composition

3. Store组合

Compose multiple stores together:
typescript
import { create, StoreApi } from 'zustand'

// Create bound stores that can access each other
function createBoundStore() {
  const useAuthStore = create<AuthStore>()((set, get) => ({
    user: null,
    login: async (credentials) => {
      const user = await api.login(credentials)
      set({ user })

      // Access cart store after login
      const cartStore = stores.cart.getState()
      await cartStore.syncCart()
    },
    logout: () => {
      set({ user: null })
      // Clear cart on logout
      stores.cart.getState().clearCart()
    },
  }))

  const useCartStore = create<CartStore>()((set, get) => ({
    items: [],
    addItem: (item) =>
      set((state) => ({ items: [...state.items, item] })),
    clearCart: () => set({ items: [] }),
    syncCart: async () => {
      const user = stores.auth.getState().user
      if (!user) return

      const items = await api.fetchCart(user.id)
      set({ items })
    },
  }))

  return {
    auth: useAuthStore,
    cart: useCartStore,
  }
}

const stores = createBoundStore()

export const useAuthStore = stores.auth
export const useCartStore = stores.cart
将多个Store组合在一起:
typescript
import { create, StoreApi } from 'zustand'

// 创建可互相访问的绑定Store
function createBoundStore() {
  const useAuthStore = create<AuthStore>()((set, get) => ({
    user: null,
    login: async (credentials) => {
      const user = await api.login(credentials)
      set({ user })

      // 登录后访问购物车Store
      const cartStore = stores.cart.getState()
      await cartStore.syncCart()
    },
    logout: () => {
      set({ user: null })
      // 登出时清空购物车
      stores.cart.getState().clearCart()
    },
  }))

  const useCartStore = create<CartStore>()((set, get) => ({
    items: [],
    addItem: (item) =>
      set((state) => ({ items: [...state.items, item] })),
    clearCart: () => set({ items: [] }),
    syncCart: async () => {
      const user = stores.auth.getState().user
      if (!user) return

      const items = await api.fetchCart(user.id)
      set({ items })
    },
  }))

  return {
    auth: useAuthStore,
    cart: useCartStore,
  }
}

const stores = createBoundStore()

export const useAuthStore = stores.auth
export const useCartStore = stores.cart

4. React Context Integration

4. React Context集成

Use Zustand with React Context for scoped stores:
typescript
import { createContext, useContext, useRef } from 'react'
import { createStore, useStore } from 'zustand'

interface TodoStore {
  todos: Todo[]
  addTodo: (text: string) => void
  toggleTodo: (id: string) => void
}

type TodoStoreApi = ReturnType<typeof createTodoStore>

const createTodoStore = (initialTodos: Todo[] = []) => {
  return createStore<TodoStore>()((set) => ({
    todos: initialTodos,
    addTodo: (text) =>
      set((state) => ({
        todos: [
          ...state.todos,
          { id: Date.now().toString(), text, completed: false },
        ],
      })),
    toggleTodo: (id) =>
      set((state) => ({
        todos: state.todos.map((todo) =>
          todo.id === id ? { ...todo, completed: !todo.completed } : todo
        ),
      })),
  }))
}

const TodoStoreContext = createContext<TodoStoreApi | null>(null)

export function TodoStoreProvider({
  children,
  initialTodos,
}: {
  children: React.ReactNode
  initialTodos?: Todo[]
}) {
  const storeRef = useRef<TodoStoreApi>()

  if (!storeRef.current) {
    storeRef.current = createTodoStore(initialTodos)
  }

  return (
    <TodoStoreContext.Provider value={storeRef.current}>
      {children}
    </TodoStoreContext.Provider>
  )
}

export function useTodoStore<T>(selector: (state: TodoStore) => T): T {
  const store = useContext(TodoStoreContext)

  if (!store) {
    throw new Error('useTodoStore must be used within TodoStoreProvider')
  }

  return useStore(store, selector)
}

// Usage
function App() {
  return (
    <TodoStoreProvider initialTodos={[]}>
      <TodoList />
    </TodoStoreProvider>
  )
}

function TodoList() {
  const todos = useTodoStore((state) => state.todos)
  const addTodo = useTodoStore((state) => state.addTodo)

  return (
    <div>
      {todos.map((todo) => (
        <div key={todo.id}>{todo.text}</div>
      ))}
      <button onClick={() => addTodo('New todo')}>Add</button>
    </div>
  )
}
结合Zustand与React Context实现作用域Store:
typescript
import { createContext, useContext, useRef } from 'react'
import { createStore, useStore } from 'zustand'

interface TodoStore {
  todos: Todo[]
  addTodo: (text: string) => void
  toggleTodo: (id: string) => void
}

type TodoStoreApi = ReturnType<typeof createTodoStore>

const createTodoStore = (initialTodos: Todo[] = []) => {
  return createStore<TodoStore>()((set) => ({
    todos: initialTodos,
    addTodo: (text) =>
      set((state) => ({
        todos: [
          ...state.todos,
          { id: Date.now().toString(), text, completed: false },
        ],
      })),
    toggleTodo: (id) =>
      set((state) => ({
        todos: state.todos.map((todo) =>
          todo.id === id ? { ...todo, completed: !todo.completed } : todo
        ),
      })),
  }))
}

const TodoStoreContext = createContext<TodoStoreApi | null>(null)

export function TodoStoreProvider({
  children,
  initialTodos,
}: {
  children: React.ReactNode
  initialTodos?: Todo[]
}) {
  const storeRef = useRef<TodoStoreApi>()

  if (!storeRef.current) {
    storeRef.current = createTodoStore(initialTodos)
  }

  return (
    <TodoStoreContext.Provider value={storeRef.current}>
      {children}
    </TodoStoreContext.Provider>
  )
}

export function useTodoStore<T>(selector: (state: TodoStore) => T): T {
  const store = useContext(TodoStoreContext)

  if (!store) {
    throw new Error('useTodoStore必须在TodoStoreProvider内部使用')
  }

  return useStore(store, selector)
}

// 用法
function App() {
  return (
    <TodoStoreProvider initialTodos={[]}>
      <TodoList />
    </TodoStoreProvider>
  )
}

function TodoList() {
  const todos = useTodoStore((state) => state.todos)
  const addTodo = useTodoStore((state) => state.addTodo)

  return (
    <div>
      {todos.map((todo) => (
        <div key={todo.id}>{todo.text}</div>
      ))}
      <button onClick={() => addTodo('新待办')}>添加</button>
    </div>
  )
}

5. Derived State with Selectors

5. 带选择器的派生状态

Create memoized derived state:
typescript
import { create } from 'zustand'
import { shallow } from 'zustand/shallow'

interface Store {
  items: Item[]
  filter: 'all' | 'active' | 'completed'
  sortBy: 'name' | 'date'
}

const useStore = create<Store>()((set) => ({ /* ... */ }))

// Memoized selector
const selectFilteredAndSortedItems = (state: Store) => {
  let items = state.items

  // Filter
  if (state.filter === 'active') {
    items = items.filter((item) => !item.completed)
  } else if (state.filter === 'completed') {
    items = items.filter((item) => item.completed)
  }

  // Sort
  if (state.sortBy === 'name') {
    items = [...items].sort((a, b) => a.name.localeCompare(b.name))
  } else {
    items = [...items].sort((a, b) => b.date.getTime() - a.date.getTime())
  }

  return items
}

// Usage
function ItemList() {
  const items = useStore(selectFilteredAndSortedItems)
  return <div>{items.map((item) => <Item key={item.id} item={item} />)}</div>
}
创建记忆化的派生状态:
typescript
import { create } from 'zustand'
import { shallow } from 'zustand/shallow'

interface Store {
  items: Item[]
  filter: 'all' | 'active' | 'completed'
  sortBy: 'name' | 'date'
}

const useStore = create<Store>()((set) => ({ /* ... */ }))

// 记忆化选择器
const selectFilteredAndSortedItems = (state: Store) => {
  let items = state.items

  // 过滤
  if (state.filter === 'active') {
    items = items.filter((item) => !item.completed)
  } else if (state.filter === 'completed') {
    items = items.filter((item) => item.completed)
  }

  // 排序
  if (state.sortBy === 'name') {
    items = [...items].sort((a, b) => a.name.localeCompare(b.name))
  } else {
    items = [...items].sort((a, b) => b.date.getTime() - a.date.getTime())
  }

  return items
}

// 用法
function ItemList() {
  const items = useStore(selectFilteredAndSortedItems)
  return <div>{items.map((item) => <Item key={item.id} item={item} />)}</div>
}

Examples

示例

WebSocket Integration

WebSocket集成

typescript
interface ChatStore {
  messages: Message[]
  isConnected: boolean
  connect: () => void
  disconnect: () => void
  sendMessage: (text: string) => void
}

const useChatStore = create<ChatStore>()((set, get) => {
  let ws: WebSocket | null = null

  return {
    messages: [],
    isConnected: false,

    connect: () => {
      ws = new WebSocket('wss://chat.example.com')

      ws.onopen = () => {
        set({ isConnected: true })
      }

      ws.onmessage = (event) => {
        const message = JSON.parse(event.data)
        set((state) => ({
          messages: [...state.messages, message],
        }))
      }

      ws.onclose = () => {
        set({ isConnected: false })
      }

      ws.onerror = (error) => {
        console.error('WebSocket error:', error)
        set({ isConnected: false })
      }
    },

    disconnect: () => {
      ws?.close()
      ws = null
      set({ isConnected: false })
    },

    sendMessage: (text) => {
      if (!ws || ws.readyState !== WebSocket.OPEN) return

      const message = {
        id: Date.now().toString(),
        text,
        timestamp: new Date(),
        userId: 'current-user',
      }

      ws.send(JSON.stringify(message))

      // Optimistically add to messages
      set((state) => ({
        messages: [...state.messages, message],
      }))
    },
  }
})
typescript
interface ChatStore {
  messages: Message[]
  isConnected: boolean
  connect: () => void
  disconnect: () => void
  sendMessage: (text: string) => void
}

const useChatStore = create<ChatStore>()((set, get) => {
  let ws: WebSocket | null = null

  return {
    messages: [],
    isConnected: false,

    connect: () => {
      ws = new WebSocket('wss://chat.example.com')

      ws.onopen = () => {
        set({ isConnected: true })
      }

      ws.onmessage = (event) => {
        const message = JSON.parse(event.data)
        set((state) => ({
          messages: [...state.messages, message],
        }))
      }

      ws.onclose = () => {
        set({ isConnected: false })
      }

      ws.onerror = (error) => {
        console.error('WebSocket错误:', error)
        set({ isConnected: false })
      }
    },

    disconnect: () => {
      ws?.close()
      ws = null
      set({ isConnected: false })
    },

    sendMessage: (text) => {
      if (!ws || ws.readyState !== WebSocket.OPEN) return

      const message = {
        id: Date.now().toString(),
        text,
        timestamp: new Date(),
        userId: 'current-user',
      }

      ws.send(JSON.stringify(message))

      // 乐观添加到消息列表
      set((state) => ({
        messages: [...state.messages, message],
      }))
    },
  }
})

Pagination Pattern

分页模式

typescript
interface PaginatedStore<T> {
  items: T[]
  page: number
  pageSize: number
  total: number
  isLoading: boolean
  hasMore: boolean

  fetchPage: (page: number) => Promise<void>
  nextPage: () => Promise<void>
  prevPage: () => Promise<void>
  reset: () => void
}

function createPaginatedStore<T>(
  fetcher: (page: number, pageSize: number) => Promise<{ items: T[]; total: number }>,
  pageSize: number = 20
) {
  return create<PaginatedStore<T>>()((set, get) => ({
    items: [],
    page: 1,
    pageSize,
    total: 0,
    isLoading: false,

    get hasMore() {
      const { page, pageSize, total } = get()
      return page * pageSize < total
    },

    fetchPage: async (page) => {
      set({ isLoading: true })

      try {
        const { items, total } = await fetcher(page, get().pageSize)
        set({ items, page, total, isLoading: false })
      } catch (error) {
        set({ isLoading: false })
        throw error
      }
    },

    nextPage: async () => {
      const { page, hasMore } = get()
      if (!hasMore) return

      await get().fetchPage(page + 1)
    },

    prevPage: async () => {
      const { page } = get()
      if (page <= 1) return

      await get().fetchPage(page - 1)
    },

    reset: () =>
      set({
        items: [],
        page: 1,
        total: 0,
        isLoading: false,
      }),
  }))
}

// Usage
const useProductStore = createPaginatedStore<Product>(
  async (page, pageSize) => {
    const response = await fetch(
      `/api/products?page=${page}&pageSize=${pageSize}`
    )
    return response.json()
  }
)
typescript
interface PaginatedStore<T> {
  items: T[]
  page: number
  pageSize: number
  total: number
  isLoading: boolean
  hasMore: boolean

  fetchPage: (page: number) => Promise<void>
  nextPage: () => Promise<void>
  prevPage: () => Promise<void>
  reset: () => void
}

function createPaginatedStore<T>(
  fetcher: (page: number, pageSize: number) => Promise<{ items: T[]; total: number }>,
  pageSize: number = 20
) {
  return create<PaginatedStore<T>>()((set, get) => ({
    items: [],
    page: 1,
    pageSize,
    total: 0,
    isLoading: false,

    get hasMore() {
      const { page, pageSize, total } = get()
      return page * pageSize < total
    },

    fetchPage: async (page) => {
      set({ isLoading: true })

      try {
        const { items, total } = await fetcher(page, get().pageSize)
        set({ items, page, total, isLoading: false })
      } catch (error) {
        set({ isLoading: false })
        throw error
      }
    },

    nextPage: async () => {
      const { page, hasMore } = get()
      if (!hasMore) return

      await get().fetchPage(page + 1)
    },

    prevPage: async () => {
      const { page } = get()
      if (page <= 1) return

      await get().fetchPage(page - 1)
    },

    reset: () =>
      set({
        items: [],
        page: 1,
        total: 0,
        isLoading: false,
      }),
  }))
}

// 用法
const useProductStore = createPaginatedStore<Product>(
  async (page, pageSize) => {
    const response = await fetch(
      `/api/products?page=${page}&pageSize=${pageSize}`
    )
    return response.json()
  }
)

Computed Properties with Getters

带Getter的计算属性

typescript
interface Store {
  items: Item[]
  filter: string
  sortBy: string

  // Computed
  filteredItems: Item[]
  sortedItems: Item[]
  stats: {
    total: number
    completed: number
    active: number
  }
}

const useStore = create<Store>()((set, get) => ({
  items: [],
  filter: 'all',
  sortBy: 'date',

  get filteredItems() {
    const { items, filter } = get()
    if (filter === 'all') return items
    if (filter === 'completed') return items.filter((i) => i.completed)
    return items.filter((i) => !i.completed)
  },

  get sortedItems() {
    const { filteredItems, sortBy } = get()
    const items = [...filteredItems]

    if (sortBy === 'name') {
      return items.sort((a, b) => a.name.localeCompare(b.name))
    }

    return items.sort((a, b) => b.date.getTime() - a.date.getTime())
  },

  get stats() {
    const { items } = get()
    return {
      total: items.length,
      completed: items.filter((i) => i.completed).length,
      active: items.filter((i) => !i.completed).length,
    }
  },
}))
typescript
interface Store {
  items: Item[]
  filter: string
  sortBy: string

  // 计算属性
  filteredItems: Item[]
  sortedItems: Item[]
  stats: {
    total: number
    completed: number
    active: number
  }
}

const useStore = create<Store>()((set, get) => ({
  items: [],
  filter: 'all',
  sortBy: 'date',

  get filteredItems() {
    const { items, filter } = get()
    if (filter === 'all') return items
    if (filter === 'completed') return items.filter((i) => i.completed)
    return items.filter((i) => !i.completed)
  },

  get sortedItems() {
    const { filteredItems, sortBy } = get()
    const items = [...filteredItems]

    if (sortBy === 'name') {
      return items.sort((a, b) => a.name.localeCompare(b.name))
    }

    return items.sort((a, b) => b.date.getTime() - a.date.getTime())
  },

  get stats() {
    const { items } = get()
    return {
      total: items.length,
      completed: items.filter((i) => i.completed).length,
      active: items.filter((i) => !i.completed).length,
    }
  },
}))

Common Patterns

常见模式

Batched Updates

批量更新

Update multiple stores atomically:
typescript
function batchUpdates(updates: Array<() => void>) {
  updates.forEach((update) => update())
}

// Usage
batchUpdates([
  () => useAuthStore.setState({ user: newUser }),
  () => useCartStore.setState({ items: [] }),
  () => useNotificationStore.setState({ unread: 0 }),
])
原子化更新多个Store:
typescript
function batchUpdates(updates: Array<() => void>) {
  updates.forEach((update) => update())
}

// 用法
batchUpdates([
  () => useAuthStore.setState({ user: newUser }),
  () => useCartStore.setState({ items: [] }),
  () => useNotificationStore.setState({ unread: 0 }),
])

Error Boundary Integration

错误边界集成

typescript
interface ErrorStore {
  errors: Error[]
  addError: (error: Error) => void
  clearErrors: () => void
}

const useErrorStore = create<ErrorStore>()((set) => ({
  errors: [],
  addError: (error) =>
    set((state) => ({ errors: [...state.errors, error] })),
  clearErrors: () => set({ errors: [] }),
}))

// Error boundary
function ErrorBoundary({ children }: { children: React.ReactNode }) {
  const errors = useErrorStore((state) => state.errors)

  if (errors.length > 0) {
    return <div>Error: {errors[0].message}</div>
  }

  return <>{children}</>
}
typescript
interface ErrorStore {
  errors: Error[]
  addError: (error: Error) => void
  clearErrors: () => void
}

const useErrorStore = create<ErrorStore>()((set) => ({
  errors: [],
  addError: (error) =>
    set((state) => ({ errors: [...state.errors, error] })),
  clearErrors: () => set({ errors: [] }),
}))

// 错误边界
function ErrorBoundary({ children }: { children: React.ReactNode }) {
  const errors = useErrorStore((state) => state.errors)

  if (errors.length > 0) {
    return <div>错误: {errors[0].message}</div>
  }

  return <>{children}</>
}

Anti-Patterns

反模式

❌ Don't Store Derived State

❌ 不要存储派生状态

typescript
// Bad: Storing derived state
const useStore = create((set) => ({
  items: [],
  itemCount: 0, // ❌ Redundant
  addItem: (item) =>
    set((state) => ({
      items: [...state.items, item],
      itemCount: state.items.length + 1, // ❌ Manual sync
    })),
}))

// Good: Use getters for derived state
const useStore = create((set, get) => ({
  items: [],
  get itemCount() {
    return get().items.length
  },
  addItem: (item) =>
    set((state) => ({ items: [...state.items, item] })),
}))
typescript
// 错误:存储派生状态
const useStore = create((set) => ({
  items: [],
  itemCount: 0, // ❌ 冗余
  addItem: (item) =>
    set((state) => ({
      items: [...state.items, item],
      itemCount: state.items.length + 1, // ❌ 手动同步
    })),
}))

// 正确:使用Getter获取派生状态
const useStore = create((set, get) => ({
  items: [],
  get itemCount() {
    return get().items.length
  },
  addItem: (item) =>
    set((state) => ({ items: [...state.items, item] })),
}))

❌ Don't Create Circular Dependencies

❌ 不要创建循环依赖

typescript
// Bad: Circular dependencies
const useStoreA = create((set) => ({
  value: 0,
  update: () => {
    useStoreB.getState().sync() // ❌ Circular
  },
}))

const useStoreB = create((set) => ({
  value: 0,
  sync: () => {
    useStoreA.getState().update() // ❌ Circular
  },
}))
typescript
// 错误:循环依赖
const useStoreA = create((set) => ({
  value: 0,
  update: () => {
    useStoreB.getState().sync() // ❌ 循环依赖
  },
}))

const useStoreB = create((set) => ({
  value: 0,
  sync: () => {
    useStoreA.getState().update() // ❌ 循环依赖
  },
}))

❌ Don't Overuse Subscriptions

❌ 不要过度使用订阅

typescript
// Bad: Subscribing in every component
function Component() {
  useEffect(() => {
    const unsubscribe = useStore.subscribe((state) => {
      console.log(state) // ❌ Memory leak if not cleaned up
    })
    // Missing return unsubscribe
  }, [])
}

// Good: Use selectors instead
function Component() {
  const value = useStore((state) => state.value)
  return <div>{value}</div>
}
typescript
// 错误:在每个组件中订阅
function Component() {
  useEffect(() => {
    const unsubscribe = useStore.subscribe((state) => {
      console.log(state) // ❌ 未清理会导致内存泄漏
    })
    // 缺少return unsubscribe
  }, [])
}

// 正确:使用选择器替代
function Component() {
  const value = useStore((state) => state.value)
  return <div>{value}</div>
}

Related Skills

相关技能

  • zustand-store-patterns: Basic store creation and usage
  • zustand-typescript: TypeScript integration
  • zustand-middleware: Using middleware for enhanced functionality
  • zustand-store-patterns: 基础Store创建与使用
  • zustand-typescript: TypeScript集成
  • zustand-middleware: 使用中间件增强功能