Zustand状态管理模式在ReactNative中的应用Skill rn-zustand-patterns

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

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

Zustand状态管理模式在React Native中的应用

问题陈述

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) => ({
  answers: {},
  
  // 错误 - 状态在函数创建时被捕获
  saveAnswerBad: (questionId: string, value: number) => {
    setTimeout(() => {
      const answers = get().answers; // ❌ 这是可以的
      // 但如果有人将`answers`作为参数传递...
    }, 1000);
  },
  
  // 正确 - 总是使用get()获取当前状态
  saveAnswer: async (questionId: string, value: number) => {
    await someAsyncOperation();
    // 在await之后,使用get()确保当前状态
    const currentAnswers = get().answers;
    set({ answers: { ...currentAnswers, [questionId]: value } });
  },
}));

// 在组件中 - 同样的原则
function Component() {
  const answers = useStore((s) => s.answers);
  
  const handleSave = async () => {
    await delay(1000);
    // 这里的answers是过时的!在渲染时被捕获
    
    // 使用getState()获取当前值
    const current = useStore.getState().answers;
  };
}

**规则:**在任何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) => ({
  answers: {},
  
  // 错误 - 存储可能变得过时的派生状态
  totalAnswers: 0,
  updateTotalAnswers: () => {
    set({ totalAnswers: Object.keys(get().answers).length });
  },
  
  // 正确 - 在选择器中计算(始终新鲜)
  // answers: {},  // 只存储源数据
}));

// 选择器计算派生值
const totalAnswers = useStore((state) => Object.keys(state.answers).length);

// 对于昂贵的计算,在外面记忆
import { useMemo } from 'react';

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

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

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

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

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

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

模式:测试Zustand商店

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

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

const useStore = create((set, get) => ({
  ...initialState,
  
  // 动作...
  
  // 重置用于测试
  _reset: () => set(initialState),
}));

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

  it('saves answers during retake flow', async () => {
    const store = useStore.getState();
    
    // 完整的异步流程
    await store.loadCompletedAnswers(assessmentId);
    await store.enableSkillAreaRetake('fundamentals');
    
    // 在异步之后验证状态
    expect(store.getState().retakeAreas).toContain('fundamentals');
    
    // 继续流程
    await store.saveAnswer('q1', 4);
    
    // 验证最终状态
    expect(useStore.getState().userAnswers['q1']).toBe(4);
  });
});

模式:调试状态变化

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

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

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

// 特定调试的手动日志记录
const useStore = create((set, get) => ({
  answers: {},
  
  saveAnswer: (questionId: string, value: number) => {
    console.log('[saveAnswer] Before:', {
      questionId,
      value,
      currentAnswers: get().answers,
      retakeAreas: get().retakeAreas,
    });
    
    set((state) => ({
      answers: { ...state.answers, [questionId]: value },
    }));
    
    console.log('[saveAnswer] After:', {
      answers: get().answers,
    });
  },
}));

常见陷阱

陷阱 解决方案
在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) => ({...}));