TS 数据驱动类型构造 export type Type = typeof DATA[keyof typeof DATA];

因为 TypeScript 类型信息默认不会保留到运行时,所以现代 TS 更倾向于让运行时数据作为唯一来源,再由数据推导类型,而不是先定义类型,再额外维护一份运行时类型元数据。而后端类型优先则需要把类型反射为数据。

核心是既要支持静态类型检查,也要类型元数据可以被传递。

现代前端更倾向于“运行时数据驱动 + 编译期类型增强”

以 JavaScript 数据作为事实来源(data as source of truth),TypeScript 类型从数据中推导出来。

ts 的类型转为 js 会消解,所以直接在js建类型数据,ts从中生成。则类型信息可以保留。这样同一份数据既保留了运行时能力,又提供了编译期类型信息。

在传统强类型语言中,我们通常习惯:

先定义类型,再根据类型创建数据。

例如 Java、C# 中:

Type / Class
      |
      ↓
 Runtime Object

类型是程序的核心模型,运行时对象根据类型产生。

但是在现代前端 TypeScript 开发中,流行另一种模式:

运行时数据驱动 + 编译期类型增强

即:

以 JavaScript 数据作为事实来源(Data as Source of Truth),TypeScript 类型从数据中推导出来。

Runtime Data
      |
      +----------------+
      |                |
      ↓                ↓
 JavaScript        TypeScript
 运行时使用          类型检查

这也是为什么现代 React、Vue、Zustand、TanStack 等生态大量使用:

as const
typeof
keyof

组合模式。


一、传统类型优先的问题

假设定义一个状态:

type Status =
  | "idle"
  | "ready"
  | "loading";

这个类型只能用于编译期检查。

例如:

function updateStatus(status: Status) {

}

可以:

updateStatus("idle");

但是:

Status

运行时不存在。

因为 TypeScript 类型会在编译后消失:

type Status =
  | "idle"
  | "ready"
  | "loading";

编译:

// 什么都没有

所以无法:

Object.values(Status);

也无法:

for (const status of Status) {}

静态类型校验的类型信息在编译后被消解,元信息需要单独维护表,容易造成不一致。


二、如果需要运行时数据怎么办?

例如前端需要:

  • 渲染下拉列表
  • 状态校验
  • 生成菜单
  • 显示标签
  • 判断接口返回值

需要:

const statuses = [
  "idle",
  "ready",
  "loading"
];

于是出现:

Status 类型
    |
    | idle
    | ready
    | loading


statuses 数据
    |
    | idle
    | ready
    | loading

两个来源。

问题:

如果新增:

"error"

可能只修改其中一个。

类型:

type Status =
  | "idle"
  | "ready"
  | "loading"
  | "error";

但是数据:

const statuses = [
  "idle",
  "ready",
  "loading"
];

运行时缺少。


三、现代前端的数据驱动方式

现代 TypeScript 更倾向:

不从类型生成数据,而是从数据生成类型。

例如:

export const STATUS = {
  IDLE: "idle",
  READY: "ready",
  LOADING: "loading",
  ERROR: "error",
} as const;

这里:

STATUS 是真实 JavaScript 对象。

运行时存在:

STATUS.IDLE

结果:

"idle"

可以:

Object.values(STATUS);

得到:

[
  "idle",
  "ready",
  "loading",
  "error"
]

四、关键:由值生成类型

核心代码:

export type Status =
  (typeof STATUS)[keyof typeof STATUS];

这是现代 TypeScript 中非常重要的模式。

拆开理解。


1. typeof STATUS

注意这里不是 JavaScript 的:

typeof STATUS

而是 TypeScript 类型位置中的 typeof

得到:

{
  readonly IDLE: "idle";
  readonly READY: "ready";
  readonly LOADING: "loading";
  readonly ERROR: "error";
}

2. keyof typeof STATUS

获取所有属性名:

"IDLE"
|
"READY"
|
"LOADING"
|
"ERROR"

也就是:

keyof typeof STATUS

结果:

"IDLE" | "READY" | "LOADING" | "ERROR"

3. 索引访问类型

继续:

(typeof STATUS)[keyof typeof STATUS]

等价于:

typeof STATUS[
  "IDLE"
  |
  "READY"
  |
  "LOADING"
  |
  "ERROR"
]

TypeScript 会自动展开:

typeof STATUS["IDLE"]
|
typeof STATUS["READY"]
|
typeof STATUS["LOADING"]
|
typeof STATUS["ERROR"]

得到:

"idle"
|
"ready"
|
"loading"
|
"error"

最终:

export type Status =
  "idle"
  | "ready"
  | "loading"
  | "error";

五、as const 的作用

为什么需要:

as const

普通:

const STATUS = {
  IDLE: "idle"
};

TypeScript 推断:

{
  IDLE: string;
}

也就是:

任何字符串都可以

但是:

const STATUS = {
  IDLE: "idle"
} as const;

推断:

{
  readonly IDLE: "idle";
}

保留字面量类型。

因此:

typeof STATUS[keyof typeof STATUS]

才能得到:

"idle"

而不是:

string

六、类型优先 vs 数据驱动

类型优先

流程:

Type
 |
 ↓
Runtime Data

需要:

  • 反射
  • 代码生成
  • Schema
  • 元数据

例如:

Java:

Class
 |
 ↓
Reflection
 |
 ↓
Runtime Metadata

类型本身可以被读取。


数据驱动

TypeScript 更常见:

Data
 |
 +---- Runtime
 |
 +---- Type

例如:

const STATUS = {
  IDLE: "idle",
  READY: "ready"
} as const;


export type Status =
  (typeof STATUS)[keyof typeof STATUS];

一份数据:

同时提供:

运行时:

STATUS.IDLE
Object.values(STATUS)

编译期:

Status

七、完整实践

例如状态管理:

export const STATUS = {
  IDLE: "idle",
  READY: "ready",
  LOADING: "loading",
  ERROR: "error",
} as const;


export type Status =
  (typeof STATUS)[keyof typeof STATUS];


const STATUS_LABELS: Record<Status, string> = {
  [STATUS.IDLE]: "Idle",
  [STATUS.READY]: "Ready",
  [STATUS.LOADING]: "Loading",
  [STATUS.ERROR]: "Error",
};

这里:

STATUS
 |
 +--> JavaScript运行时数据
 |
 +--> Status类型


Status
 |
 +--> 约束 STATUS_LABELS

总结

  • JavaScript 数据天然存在于运行时
  • TypeScript 类型只存在于编译期(附加推导校验)
  • TS是JS数据驱动的类型校验

因此:

const DATA = {...} as const;

export type Type =
  typeof DATA[keyof typeof DATA];

一段话

要获取元数据,在JS比较特殊。

反射是由强类型导出,而JS原生无强类型,由数据驱动。

所以就需要将类型信息直接作为数据,由TS导出为类型提供校验。

如此单一数据源,运行期有类型信息,编译期也有类型供校验

posted @ 2026-07-25 05:25  丘狸尾  阅读(9)  评论(0)    收藏  举报