跳转到内容

状态管理规范

全局状态、局部状态管理


  • Requirement:状态按作用域分为三层,禁止跨层滥用。
层级作用域管理方式示例
局部状态单个组件内useState / useReducer(React)、ref / reactive(Vue)表单输入、弹窗开关、loading
共享状态多个组件共享全局 Store(Pinia / Redux / Zustand)用户信息、权限、主题
服务端状态来自后端的数据请求库缓存(TanStack Query / SWR)列表数据、详情数据
  • Requirement:优先使用局部状态,只有在多个组件需要共享时才提升为全局状态。
  • Standard:服务端数据(来自接口)不放入全局 Store,使用 TanStack Query / SWR 等请求库管理缓存。
  • Caution:禁止将页面的临时筛选条件、表单草稿放入全局 Store,刷新后会丢失且污染全局。
这个状态需要被多个不相关的组件共享吗?
├── 是 → 放入全局 Store
└── 否
├── 这个数据来自后端接口吗?
│ ├── 是 → 用 TanStack Query / SWR 管理
│ └── 否 → 组件局部状态(useState / ref)
反模式问题建议
所有状态都放全局 StoreStore 膨胀、不必要的重渲染优先局部状态
接口数据放全局 Store缓存管理复杂用请求库管理
局部状态提升到父组件层层传递prop drilling用 Context 或 Store

  • Standard:优先使用现代状态管理库:
    • React:Zustand(轻量)或 Redux Toolkit(大型应用)
    • Vue:Pinia
  • Requirement:禁止直接使用 localStorage 作为状态管理,需通过 Store 持久化插件统一处理。
  • Standard:按业务模块拆分 Store,每个 Store 包含 state、getters、actions 三部分。
  • Standard:Store 文件放在 src/store/ 目录下,按模块命名,如 user.ts、app.ts、permission.ts。
src/store/
├── index.ts # Store 入口,统一导出
├── user.ts # 用户状态
├── permission.ts # 权限状态
├── app.ts # 应用级状态(主题、语言)
└── cart.ts # 业务状态(购物车)
// src/store/user.ts
import { create } from 'zustand';
 
interface UserState {
  id: string;
  name: string;
  avatar: string;
  token: string;
  isLoggedIn: () => boolean;
  setUser: (user: Partial<UserState>) => void;
  clearUser: () => void;
}
 
const initialState = {
  id: '',
  name: '',
  avatar: '',
  token: '',
};
 
export const useUserStore = create<UserState>((set, get) => ({
  ...initialState,
  isLoggedIn: () => !!get().token,
  setUser: (user) => set(user),
  clearUser: () => set(initialState),
}));
// src/store/user.ts
import { defineStore } from 'pinia';
 
export const useUserStore = defineStore('user', {
  state: () => ({
    id: '',
    name: '',
    avatar: '',
    token: '',
  }),
  getters: {
    isLoggedIn: (state) => !!state.token,
  },
  actions: {
    setUser(user: Partial<UserState>) {
      Object.assign(this, user);
    },
    clearUser() {
      this.$reset();
    },
  },
});
  • Requirement:组件中通过 selector 精确订阅需要的状态,避免订阅整个 Store 导致不必要的重渲染。
// React:精确选择
const name = useUserStore((state) => state.name);
const setUser = useUserStore((state) => state.setUser);
 
// 错误:订阅整个 store
const userStore = useUserStore();
  • Standard:异步逻辑放在 actions 中,禁止在组件内直接修改 state。
反模式问题建议
一个大 Store 存所有状态难以维护、重渲染按模块拆分
组件内直接修改 state状态变更不可追踪通过 actions 修改
订阅整个 Store不必要的重渲染使用 selector
Store 中存接口请求的列表数据缓存管理困难用请求库管理

  • Standard:组件私有状态放在组件内部,使用 useState / useReducer(React)或 ref / reactive(Vue)。
  • Standard:多个子组件共享的状态提升到最近的共同父组件,或通过 Context 传递。
  • Standard:简单状态(开关、计数)用 useState,复杂状态(多字段、有依赖关系)用 useReducer。
// 简单状态用 useState
const [visible, setVisible] = useState(false);
 
// 复杂状态用 useReducer
interface FormState {
  values: Record<string, any>;
  errors: Record<string, string>;
  submitting: boolean;
}
 
type FormAction =
  | { type: 'SET_VALUE'; field: string; value: any }
  | { type: 'SET_ERROR'; field: string; error: string }
  | { type: 'SUBMIT_START' }
  | { type: 'SUBMIT_END' };
 
function formReducer(state: FormState, action: FormAction): FormState {
  switch (action.type) {
    case 'SET_VALUE':
      return { ...state, values: { ...state.values, [action.field]: action.value } };
    case 'SET_ERROR':
      return { ...state, errors: { ...state.errors, [action.field]: action.error } };
    case 'SUBMIT_START':
      return { ...state, submitting: true };
    case 'SUBMIT_END':
      return { ...state, submitting: false };
  }
}
  • Requirement:派生状态(从其他状态计算得出)使用 useMemo / computed,禁止用额外的 state 存储。
// 正确:派生状态用 useMemo
const [list, setList] = useState<User[]>([]);
const activeCount = useMemo(
  () => list.filter((item) => item.status === 'active').length,
  [list]
);
 
// 错误:用额外 state 存派生数据
const [activeCount, setActiveCount] = useState(0);
useEffect(() => {
  setActiveCount(list.filter((item) => item.status === 'active').length);
}, [list]);
  • Standard:当两个子组件需要共享状态时,将状态提升到它们的共同父组件。
  • Caution:如果状态需要跨多层传递,使用 Context 而非逐层传递 props(prop drilling)。
反模式问题建议
派生状态用额外 state数据不同步、多余渲染用 useMemo/computed
用 useEffect 同步状态容易出现无限循环直接在事件中更新
prop drilling 超过 3 层维护困难用 Context 或 Store

  • Standard:需要持久化的状态(用户 Token、主题、语言)通过 Store 持久化插件统一处理,禁止直接操作 localStorage。
  • Requirement:敏感信息(Token、用户信息)加密存储,或使用 HttpOnly Cookie,禁止明文存 localStorage。
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
 
export const useUserStore = create<UserState>()(
  persist(
    (set, get) => ({
      ...initialState,
      setUser: (user) => set(user),
      clearUser: () => set(initialState),
    }),
    {
      name: 'user-store',
      partialize: (state) => ({ token: state.token }), // 只持久化必要字段
    }
  )
);
import { defineStore } from 'pinia';
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
 
export const useUserStore = defineStore('user', {
  state: () => ({ ... }),
  persist: {
    key: 'user-store',
    paths: ['token'], // 只持久化 token
  },
});
内容是否持久化存储方式
用户 Token是加密 localStorage 或 HttpOnly Cookie
主题/语言是localStorage
用户基本信息视情况内存为主,刷新后重新获取
表单草稿否(临时)不持久化或 sessionStorage
列表数据否请求库缓存
反模式问题建议
Token 明文存 localStorageXSS 攻击可窃取加密存储或用 Cookie
直接在组件中操作 localStorage状态不同步通过 Store 持久化
持久化整个 Store存储冗余数据只持久化必要字段

  • Requirement:开发环境必须安装并启用状态管理调试工具:
    • Redux:Redux DevTools
    • Pinia:Vue DevTools(内置 Pinia 支持)
    • Zustand:集成 Redux DevTools 中间件
  • Standard:生产环境关闭调试工具,避免暴露内部状态。
  • Standard:所有状态变更必须通过 actions,便于在 DevTools 中追踪时间线。
  • Caution:禁止在组件内直接修改 state 对象(React 的 state 必须不可变)。
  • Suggestion:开发环境在 state 变更时输出日志,包含 action 名称、变更前后状态。
// Zustand 日志中间件
const log = (config) => (set, get, api) =>
  config(
    (args) => {
      console.log('[store] before:', get());
      set(args);
      console.log('[store] after:', get());
    },
    get,
    api
  );
反模式问题建议
生产环境开启 DevTools性能损耗、信息暴露生产环境关闭
直接修改 state 对象React 无法检测变更不可变更新
无调试工具难以定位状态问题安装 DevTools