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

TypeScript中只读Record转换后的类型推断优化及问题解析

问题分析与解决方案

问题场景

定义了包含路由信息的ROUTES对象,每个路由必填path和title,可包含额外属性:

export type TRoute = {
  path: string;
  title: string;
} & Record<string, any>;

export type TRoutes = Record<string, TRoute>;

export const ROUTES = {
  dashboard: {
    path: "/dashboard",
    title: "Dashboard"
  },
  todos: {
    path: "/todos",
    title: "Todos"
  },
  settings: {
    path: "/settings",
    title: "Settings",
    foo: 2
  }
} as const satisfies TRoutes;

编写getRoutesList函数将ROUTES转为数组:

type TValueOfRecord<T> = T[keyof T];
type TRecordOfRecord<T> = Record<string, TValueOfRecord<T>>;

const getRoutesList = <T extends TRecordOfRecord<T> & TRoutes>(routes: T) => {
  return Object.values(routes);
};

export const ROUTE_LIST = getRoutesList(ROUTES);

此时遍历ROUTE_LIST访问仅settings路由拥有的foo字段,TypeScript未报错:

ROUTE_LIST.map((route) => {
  console.log(route.foo); // <- 未报错
  return route;
});

原因分析

  1. 类型定义过于宽松:TRoute中的& Record<string, any>意味着所有路由对象都允许任意字符串键的属性,TypeScript会默认认为每个路由都可能存在foo(或任何其他属性),因此访问时不会触发错误。
  2. 泛型约束冗余:getRoutesList的泛型约束T extends TRecordOfRecord<T> & TRoutes完全没必要,再加上TRoute本身的宽松定义,返回的数组元素类型被放宽为所有路由属性的超集,进一步模糊了类型边界。

重构方案

步骤1:修正类型定义,保留精确路由类型

去掉TRoute中宽松的Record<string, any>,改用BaseRoute作为基础约束,同时让ROUTES保留自身的字面量类型:

// 基础路由约束,仅定义必填字段
type BaseRoute = {
  path: string;
  title: string;
};

// 定义ROUTES,用satisfies确保每个路由符合BaseRoute,同时保留独有属性
export const ROUTES = {
  dashboard: {
    path: "/dashboard",
    title: "Dashboard"
  },
  todos: {
    path: "/todos",
    title: "Todos"
  },
  settings: {
    path: "/settings",
    title: "Settings",
    foo: 2
  }
} as const satisfies Record<string, BaseRoute>;

// 自动推导路由类型
export type TRoutes = typeof ROUTES;
export type TRoute = TRoutes[keyof TRoutes];

步骤2:简化getRoutesList函数

不需要复杂泛型约束,直接返回输入对象值的联合类型数组:

const getRoutesList = <T extends Record<string, BaseRoute>>(routes: T) => {
  return Object.values(routes) as (T[keyof T])[];
};

export const ROUTE_LIST = getRoutesList(ROUTES);
// 此时ROUTE_LIST的类型为各路由字面量类型的联合数组,类型精确

步骤3:安全访问独有属性

现在TypeScript会准确识别每个路由的属性,访问不存在的属性会报错,需要通过类型守卫处理:

ROUTE_LIST.map((route) => {
  // 直接访问route.foo会报错:属性'foo'在部分路由类型上不存在
  if ('foo' in route) {
    console.log(route.foo); // 类型守卫后可安全访问
  }
  return route;
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 19:37:50