如何在 React 函数组件外部安全访问 Zustand Store

如何在 React 函数组件外部安全访问 Zustand Store

Zustand 的 useStore Hook 不能在顶层调用,但可通过 getState() 和 setState() 方法在工具函数、副作用、API 调用等非组件上下文中安全读写状态。

zustand 的 `usestore` hook 不能在顶层调用,但可通过 `getstate()` 和 `setstate()` 方法在工具函数、副作用、api 调用等非组件上下文中安全读写状态。

Zustand 的核心设计哲学之一是“状态即数据”,其 store 实例本身是可直接操作的独立对象——它不仅支持 Hook 形式(useStore()),更提供了面向普通 JavaScript 环境的底层 API:getState()、setState() 和 subscribe()。这意味着你完全可以在 .ts 工具文件、事件回调、定时器、Axios 拦截器甚至 Web Worker 中与 store 交互,而无需依赖 React 渲染上下文。

✅ 正确做法:使用 getState() 和 setState()

将你的 util.ts 改写为如下方式(关键改动已高亮):

// @/store/useStore.ts import { create } from 'zustand';  interface Room {   room_no: number;   adult: number;   child: number;   infant: number; }  interface StoreState {   roomState: Room[];   setRoomState: (rooms: Room[]) => void; }  const useStore = create<StoreState>((set) => ({   roomState: [],   setRoomState: (rooms) => set({ roomState: rooms }), }));  export default useStore;
// @/utils/util.ts import useStore from '@/store/useStore';  // ✅ 安全:直接调用 store 方法,不使用 Hook const utilityFunctions = {   sha512: async (str: string): Promise<string> => {     const buf = await crypto.subtle.digest('SHA-512', new TextEncoder().encode(str));     return Array.from(new Uint8Array(buf))       .map((b) => b.toString(16).padStart(2, '0'))       .join('');   },    addRoom: (min_inv: number) => {     const currentState = useStore.getState(); // ? 读取当前 state     if (currentState.roomState.length < min_inv) {       useStore.setState({         roomState: [           ...currentState.roomState,           {             room_no: currentState.roomState.length + 1,             adult: 0,             child: 0,             infant: 0,           },         ],       });     }   },    // ✅ 更推荐:将 state 更新逻辑封装进 store 内部(提升可维护性)   addRoomSafely: (min_inv: number) => {     useStore.setState((state) => {       if (state.roomState.length >= min_inv) return state;       return {         roomState: [           ...state.roomState,           {             room_no: state.roomState.length + 1,             adult: 0,             child: 0,             infant: 0,           },         ],       };     });   }, };  export default utilityFunctions;

⚠️ 注意事项与最佳实践

  • 不要解构 useStore() 返回值到顶层:如 const { roomState, setRoomState } = useStore() 是非法的,会触发 ESLint react-hooks/rules-of-hooks 报错。
  • 优先使用 setState(callback) 形式:它接收前一个 state,保证更新基于最新快照(尤其在异步或高频调用场景下避免竞态)。
  • 避免在工具函数中长期持有 getState() 返回的引用:Zustand 的 getState() 返回的是当前快照(浅拷贝),不是响应式引用;如需监听变化,请用 subscribe()(适用于非组件逻辑,如日志、分析上报)。
  • 类型安全提示:确保 create 的泛型声明完整,TypeScript 将自动推导 getState() 和 setState() 的类型,大幅提升开发体验。

✅ 总结

Zustand 天然支持「脱离 React 组件」的状态管理。只需放弃在工具层调用 Hook 的惯性思维,转而使用 useStore.getState() 读取、useStore.setState() 写入,即可优雅解决跨上下文状态操作问题。这不仅消除了 ESLint 报错,更让状态逻辑真正实现关注点分离——UI 层专注渲染,工具层专注业务,store 本身成为统一、可测试、可复用的状态中枢。