全局状态配合 TypeScript 的核心是全链路类型约束:通过接口建模状态结构、类型守卫校验可用性、泛型工具函数控制更新与消费,辅以 ESLint 和编译选项杜绝 any 与隐式松散访问。

全局状态配合 TypeScript 定义类型,核心是让状态结构、变更逻辑和消费场景全部可推导、可约束。不是简单加个 any 或 any[] 就完事,而是通过接口建模 + 类型守卫 + 工具函数泛型,把“状态长什么样”“能怎么改”“改完怎么用”全链路锁死。
用 interface 或 type 精确描述状态结构
避免用 Record 或 object,而是按真实业务字段定义可读、可复用的类型:
- 用户模块状态可定义为 interface UserState,包含
id: string、name: string | null、roles: RoleType[](RoleType是联合字面量类型) - 加载状态建议拆出 interface LoadingState,如
{ pending: boolean; error: string | null },而非笼统的loading: boolean - 嵌套结构用 type DeepPartial
或递归工具类型控制可选深度,避免 state.user?.profile?.avatar?.url这类“侥幸访问”
状态更新方法必须带类型签名,禁用隐式 any
无论是 Redux Toolkit 的 createSlice、Zustand 的 create,还是手写事件总线,所有修改状态的方法都应明确输入输出类型:
- Redux Toolkit 中,
reducers的每个函数参数必须标注state: Draft, action: PayloadAction - Zustand 中,
set调用推荐用函数式更新,并标注泛型:set((state) => ({ ...state, user: { ...state.user, name: payload } })),TS 会自动校验字段是否存在、类型是否匹配 - 自定义
updateState(path, value)工具函数时,用模板字面量类型约束path,例如type PathOf,让= T extends object ? { [K in keyof T]: `${K}` | `${K}.${PathOf }` }[keyof T] : never updateState('user.name', 'Alice')在路径错误时直接报错
消费端用类型守卫 + 泛型 Hook 实现零误用提示
组件里取状态不能只靠解构或 state.xxx 直接访问,要让 TS 主动告诉你“这个字段当前是否可用”:
- 对可能为空的状态(如异步加载中的 user),定义 const isUserLoaded = (s: GlobalState): s is StateWithUser => !!s.user,在
if (isUserLoaded(state)) { state.user.name }中获得完整类型收窄 - 封装 useTypedSelector
(selector: (s: GlobalState) => T) ,让 selector 返回值类型自动参与推导,避免手动断言as string - 状态派生计算(如
activeOrders)统一用 computed 函数包裹,并标注返回类型:computed(() => state.orders.filter(o => o.status === 'active')) as Order[],既保类型又利复用
配合 ESLint + TypeScript 插件堵住常见松散漏洞
光靠手写类型不够,需工具链辅助落地:
- 启用
@typescript-eslint/no-explicit-any并设为 error,强制替换所有any为具体类型或unknown - 开启
noUncheckedIndexedAccess编译选项,让arr[0]默认返回T | undefined,倒逼你处理空值分支 - 在 CI 中运行
tsc --noEmit,确保所有状态相关文件通过类型检查,尤其关注slice.ts、store.ts、hooks/useGlobalState.ts等核心模块










