ReactZustand状态管理模式Skill react-zustand-patterns

本文档提供了React中使用Zustand状态管理库时的常见模式和最佳实践,包括同步设置与异步渲染、避免过时闭包、异步操作处理、选择器稳定性、派生状态计算、商店订阅和副作用处理、Zustand商店的测试以及调试状态变化。这些模式有助于理解和解决在使用Zustand时可能遇到的各种状态管理问题。

前端开发 0 次安装 0 次浏览 更新于 3/3/2026

name: react-zustand-patterns description: Zustand state management patterns for React. Use when working with Zustand stores, debugging state timing issues, or implementing async actions. Works for both React web and React Native.

Zustand Patterns for React

问题陈述

Zustand的简单性隐藏了重要的时间细节。set()是同步的,但React的重渲染是批量的。getState()逃避了过时的闭包。商店中的异步操作需要仔细处理。理解这些内部机制可以防止微妙的错误。


模式:set()是同步的,渲染是批量的

**问题:**假设在set()之后状态立即对React“准备好”。

const useStore = create((set, get) => ({
  count: 0,

  increment: () => {
    set({ count: get().count + 1 });
    // 状态在这里已经更新(set是同步的)
    console.log(get().count); // ✅显示新值

    // 但React还没有重新渲染
    // 组件将看到旧值,直到下一个渲染周期
  },
}));

关键见解:

  • set()同步更新商店
  • getState()立即反映新值
  • React组件异步重新渲染(批量)

何时重要:

  • 链式多个状态更新
  • 更新后验证状态
  • 调试“过时”的组件值

模式:getState()逃避过时的闭包

**问题:**回调和异步函数在创建时捕获状态。使用get()getState()始终获得当前状态。

const useStore = create((set, get) => ({
  data: {},

  // 错误 - 闭包捕获过时状态
  saveDataBad: (id: string, value: number) => {
    setTimeout(() => {
      // 如果有人将`data`作为参数传递,它将是过时的
    }, 1000);
  },

  // 正确 - 总是使用get()获取当前状态
  saveData: async (id: string, value: number) => {
    await someAsyncOperation();
    // 在await之后,使用get()确保当前状态
    const currentData = get().data;
    set({ data: { ...currentData, [id]: value } });
  },
}));

// 在组件中 - 同样的原则
function Component() {
  const data = useStore((s) => s.data);

  const handleSave = async () => {
    await delay(1000);
    // data在这里是过时的!在渲染时捕获

    // 使用getState()获取当前值
    const current = useStore.getState().data;
  };
}

**规则:**在任何await之后,使用get()getState() - 永远不要依赖闭包捕获的值。


模式:商店中的异步操作

**问题:**异步操作需要显式的async/await和在awaits之后仔细读取状态。

const useStore = create((set, get) => ({
  loading: false,
  data: null,
  error: null,

  // 错误 - 没有async关键字,容易产生竞态条件
  fetchDataBad: (id: string) => {
    set({ loading: true });
    api.fetch(id).then((data) => {
      set({ data, loading: false });
    });
    // 立即返回,调用者无法等待
  },

  // 正确 - 适当的异步操作
  fetchData: async (id: string) => {
    set({ loading: true, error: null });

    try {
      const data = await api.fetch(id);
      // 在await之后如果需要,重新读取状态
      if (get().loading) { // 检查我们是否仍在加载状态
        set({ data, loading: false });
      }
    } catch (error) {
      set({ error: error.message, loading: false });
    }
  },
}));

// 调用者可以正确等待
await useStore.getState().fetchData('123');

模式:选择器稳定性

**问题:**选择器创建新对象导致不必要的重新渲染。

// 错误 - 每次渲染都创建新对象
const data = useStore((state) => ({
  name: state.name,
  count: state.count,
}));

// 正确 - 使用多个选择器
const name = useStore((state) => state.name);
const count = useStore((state) => state.count);

// 或者 - 使用浅比较(Zustand 4.x)
import { shallow } from 'zustand/shallow';

const { name, count } = useStore(
  (state) => ({ name: state.name, count: state.count }),
  shallow
);

// Zustand 5.x - 使用useShallow钩子
import { useShallow } from 'zustand/react/shallow';

const { name, count } = useStore(
  useShallow((state) => ({ name: state.name, count: state.count }))
);

模式:派生状态

**问题:**在选择器中计算派生值与存储它们。

const useStore = create((set, get) => ({
  items: [],

  // 错误 - 存储可能过时的派生状态
  totalItems: 0,
  updateTotalItems: () => {
    set({ totalItems: get().items.length });
  },
}));

// 正确 - 在选择器中计算(始终新鲜)
const totalItems = useStore((state) => state.items.length);

// 对于昂贵的计算,在商店外部进行记忆
import { useMemo } from 'react';

function Component() {
  const items = useStore((state) => state.items);
  const expensiveResult = useMemo(() => {
    return computeExpensiveAnalysis(items);
  }, [items]);
}

模式:商店订阅用于副作用

**问题:**需要在React组件之外响应状态变化。

// 订阅特定状态变化
const unsubscribe = useStore.subscribe(
  (state) => state.data,
  (data, prevData) => {
    console.log('Data changed:', { prev: prevData, current: data });
    // 持久化到存储,发送分析等。
  },
  { equalityFn: shallow }
);

// 在Zustand 4.x中使用subscribeWithSelector中间件
import { subscribeWithSelector } from 'zustand/middleware';

const useStore = create(
  subscribeWithSelector((set, get) => ({
    data: {},
    // ...
  }))
);

模式:测试Zustand商店

**问题:**测试需要重置商店状态并验证异步流程。

// 带有重置功能的商店
const initialState = {
  data: {},
  loading: false,
};

const useStore = create((set, get) => ({
  ...initialState,

  // 动作...

  // 重置用于测试
  _reset: () => set(initialState),
}));

// 测试
describe('Data Store', () => {
  beforeEach(() => {
    useStore.getState()._reset();
  });

  it('fetches data correctly', async () => {
    const store = useStore.getState();

    await store.fetchData('123');

    expect(useStore.getState().data).toBeDefined();
    expect(useStore.getState().loading).toBe(false);
  });
});

模式:调试状态变化

**问题:**追踪状态何时何地意外变化。

// 添加日志记录中间件
import { devtools } from 'zustand/middleware';

const useStore = create(
  devtools(
    (set, get) => ({
      // ...你的商店
    }),
    { name: 'MyStore' }
  )
);

// 特定调试的手动日志记录
const useStore = create((set, get) => ({
  data: {},

  saveData: (id: string, value: number) => {
    console.log('[saveData] Before:', {
      id,
      value,
      currentData: get().data,
    });

    set((state) => ({
      data: { ...state.data, [id]: value },
    }));

    console.log('[saveData] After:', {
      data: get().data,
    });
  },
}));

模式:持久化中间件

**问题:**跨会话持久化状态。

import { persist } from 'zustand/middleware';

// Web - localStorage
const useStore = create(
  persist(
    (set, get) => ({
      preferences: {},
      setPreference: (key, value) =>
        set((state) => ({
          preferences: { ...state.preferences, [key]: value }
        })),
    }),
    {
      name: 'app-preferences',
      // 可选:选择要持久化的内容
      partialize: (state) => ({ preferences: state.preferences }),
    }
  )
);

常见陷阱

陷阱 解决方案
await后过时的闭包 在每个await后使用get()
选择器返回新对象 使用shallow或多个选择器
操作不可等待 添加async关键字,返回承诺
组件中的状态似乎过时 组件尚未重新渲染 - 使用getState()进行即时读取
无法找到状态何时变化 添加devtools中间件或手动日志记录

Zustand 5.x迁移说明

如果从4.x升级:

// 4.x - shallow来自主包
import { shallow } from 'zustand/shallow';

// 5.x - useShallow钩子用于React
import { useShallow } from 'zustand/react/shallow';

// 4.x - 通常需要类型参数
const useStore = create<StoreType>()((set, get) => ({...}));

// 5.x - 改进的类型推断
const useStore = create((set, get) => ({...}));