You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript如何基于嵌套键值实现联合类型的正确类型推断

双类型用户路由下的TS类型收窄实现方案

当前场景的核心问题是TS默认无法通过嵌套在role对象下的id字段自动完成联合类型的判别收窄,结合独立路由+React Context的架构,有3种可直接落地的方案,按改造成本从低到高排列:


方案1:自定义类型守卫,零改动适配现有数据结构

不需要修改API返回结构、不需要调整现有类型定义,通过TS的类型守卫函数,基于嵌套的role.id字段手动指定类型收窄规则即可。

实现代码

enum UserTypes {
  user = 'user',
  professionist = 'professionist'
}

// 客户端用户类型校验守卫
const isClient = (profile: UserProfile): profile is Client => {
  return profile.role.id === UserTypes.user
}

// 专业人士类型校验守卫
const isProfessionist = (profile: UserProfile): profile is Professionist => {
  return profile.role.id === UserTypes.professionist
}

路由内使用方式

在对应类型的独立路由入口组件中,先通过守卫做权限校验,校验通过后TS会自动将变量推断为对应类型,可直接访问专属字段:

// 客户端专属路由组件
const ClientDashboard = () => {
  const { userProfile } = useUserContext()
  // 非客户端用户直接跳转无权限页
  if (!isClient(userProfile)) return <Navigate to="/403" />
  // 此处userProfile自动推断为Client类型,可直接访问subscriptions等客户端专属字段
  console.log(userProfile.subscriptions)
  return <div>客户端工作台</div>
}

专业人士路由同理,替换为isProfessionist守卫即可。


方案2:顶层添加判别字段,实现自动类型收窄

如果不想每次校验都导入守卫函数,可以在API数据存入Context之前做一层格式化,将判别字段提升到对象顶层,TS可基于顶层字段自动完成类型收窄,不需要额外写守卫逻辑。

实现代码

首先调整类型定义,给两类用户都添加顶层的userType判别字段:

type Client = {
  userType: UserTypes.user; // 顶层判别字段
  role: ClientRole;
  id: string;
  first_name: string;
  last_name: string;
  display_name: string;
  email: string;
  related: RelatedUser[];
  age: number;
  city: string;
  subscriptions: any[];
};

type Professionist = {
  userType: UserTypes.professionist; // 顶层判别字段
  role: ProfessionistRole;
  id: string;
  first_name: string;
  last_name: string;
  display_name: string;
  email: string;
  related: RelatedUser[];
  badges: any[];
  tags: Tags[];
  experiences: string[];
  method: string;
  description: string;
  age: number;
  city: string;
  account: Account;
  plans: Plans[];
};

type UserProfile = Client | Professionist;

在API响应拦截器或者Context初始化的位置,对原始返回数据做格式化:

const formatUser = (rawUser: Omit<Client, 'userType'> | Omit<Professionist, 'userType'>): UserProfile => {
  return {
    ...rawUser,
    userType: rawUser.role.id
  }
}

路由内使用方式

直接判断顶层userType字段即可,TS会自动收窄类型:

const ProfessionistDashboard = () => {
  const { userProfile } = useUserContext()
  if (userProfile?.userType !== UserTypes.professionist) return <Navigate to="/403" />
  // 此处userProfile自动推断为Professionist类型,可直接访问plans、badges等专属字段
  console.log(userProfile.plans)
  return <div>专业人士工作台</div>
}

方案3:封装专属取值Hook,消除路由内重复校验

因为已经做了路由拆分,可以在Context层封装两类用户专属的取值Hook,把类型校验逻辑收敛到Hook内部,路由组件不需要重复写判断逻辑。

实现代码

基于方案1的类型守卫,封装两个专属Hook:

// 客户端路由专属取值Hook
const useClientProfile = () => {
  const { userProfile } = useUserContext()
  if (!isClient(userProfile)) {
    // 配合路由错误边界做无权限跳转即可,也可以直接在这里做跳转处理
    throw new Error("当前路由仅客户端用户可访问")
  }
  return userProfile
}

// 专业人士路由专属取值Hook
const useProfessionistProfile = () => {
  const { userProfile } = useUserContext()
  if (!isProfessionist(userProfile)) {
    throw new Error("当前路由仅专业人士用户可访问")
  }
  return userProfile
}

路由内使用方式

对应路由下直接调用对应Hook即可,拿到的直接是收窄好类型的数据:

// 客户端路由下的订单页组件
const ClientOrderPage = () => {
  const clientInfo = useClientProfile()
  // 直接访问Client类型的所有字段,不需要额外判断
  return <div>当前用户:{clientInfo.display_name}</div>
}

注意:如果采用抛错的校验方式,需要给路由组配置错误边界组件,捕获错误后跳转至对应提示页,避免页面白屏。


内容的提问来源于stack exchange,提问作者tmanuel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 00:24:41