导读:本期聚焦于乙爱丽丝创作的《TypeScript类型能当值用吗?如何获取声明类型的字面量值详解》,敬请观看详情。TypeScript的类型系统只存在于编译阶段,编译成JavaScript后所有类型信息都会被擦除,这是很多刚接触TypeScript的开发者容易踩的坑:试图在运行时读取某个类型或接口的字段名,结果发现根本拿不到。本文围绕类型与运行时值的边界展开,先解释类型擦除的底层机制,说明为什么类型不能直接当作值使用,再给出几种在运行时获取声明信息的可行方案,包括const对象满足as const推导联合类型、keyof与映射类型的配合使用、以及枚举和symbol类型的特殊地位。文中配有完整代码示例,对比各方案的适用场景和局限性,帮助你判断什么时候该用类型、什么时候必须引入运行时结构,写出既类型安全又能在运行时正确工作的代码。

在TypeScript项目里写过一段时间代码后,一个很典型的困惑会浮现出来:我明明定义了一个接口,里面写了一堆字段,为什么运行的时候没办法拿到这些字段的名字?比如想遍历接口的所有属性做表单校验,或者想把联合类型的每个成员打印出来,编译器都毫无怨言,运行时却是一片空白。这个问题的根源在于TypeScript的类型系统是编译期结构,理解这一点是掌握这门语言的关键一步。本文就来拆解类型与运行时值的边界,并给出几种在运行时获取声明信息的实操方案。

TypeScript类型能当值用吗?如何获取声明类型的字面量值详解

类型擦除:为什么类型不能当值用

首先要明确一个事实:TypeScript编译的本质是类型擦除。编译器会把类型注解、接口定义、类型别名这些内容全部剥离,只留下纯粹的JavaScript代码。你在.ts文件里写的interface User { name: string },编译后不会留下任何痕迹,产出的JS里根本不存在User这个东西。

看一个直观的对比:

// 类型声明,编译后完全消失
type Status = 'active' | 'inactive' | 'pending';

// 下面这行会直接报编译错误
// console.log(Status); // 错误:Status只是一个类型,不能当值用

// typeof 检查的永远是运行时值,而不是类型
const s: Status = 'active';
console.log(typeof s); // 输出 "string",而不是 Status

这解释了一个常见误区:很多人以为typeofinstanceof这些运算符能感知类型信息,实际上它们操作的都是运行时值。typeof返回的是JavaScript的原始类型字符串,对接口、类型别名的存在一无所知。所以任何"在运行时读取类型定义"的想法,在默认情况下都是行不通的。

不过有几个例外值得注意:enum是TypeScript中唯一会生成运行时对象的类型结构,class也会产出真实的构造函数,还有const enum会在编译时直接内联常量值。除此之外的type、interface、联合类型、映射类型统统是纯粹的编译期概念。想跨越这道边界,必须引入一个真实存在于运行时的结构。

方案一:用 const 对象加 as const 反向推导联合类型

既然类型不能生成值,那就反过来——先写值,再让类型从值推导出来。这是社区里最主流的做法,核心思路是定义一个const对象,用as const断言锁定字面量类型,然后借助typeofkeyof拿到所有键组成的联合类型。

// 运行时真实存在的对象
const DIRECTIONS = {
  up: 'UP',
  down: 'DOWN',
  left: 'LEFT',
  right: 'RIGHT',
} as const;

// 从值反推类型
type DirectionKey = keyof typeof DIRECTIONS; // 'up' | 'down' | 'left' | 'right'
type DirectionValue = typeof DIRECTIONS[keyof typeof DIRECTIONS]; // 'UP' | 'DOWN' | 'LEFT' | 'RIGHT'

// 现在运行时和类型都有了
Object.keys(DIRECTIONS).forEach((key) => {
  console.log(key, DIRECTIONS[key as DirectionKey]);
});

这个方案的优势是单一数据源:对象字面量既是运行时数据,又是类型的推导依据,改一处两边同步,不会出现类型和值不一致的尴尬。缺点是语法略显绕,typeof X[keyof typeof X]这种写法对新手不太友好,建议封装成注释清晰的基础模块。

如果需要的只是一组简单常量,还可以用数组实现同样效果:

const FRUITS = ['apple', 'banana', 'orange'] as const;
type Fruit = typeof FRUITS[number]; // 'apple' | 'banana' | 'orange'

// 运行时可以正常遍历
FRUITS.forEach((f) => console.log(f));

// 类型层面也能做穷尽检查
function getPrice(f: Fruit): number {
  switch (f) {
    case 'apple': return 5;
    case 'banana': return 3;
    case 'orange': return 4;
  }
}

方案二:枚举——唯一自带运行时对象的类型结构

enum是TypeScript给这道边界开的官方口子。枚举定义会编译成一个真实的JavaScript对象,因此在运行时可以访问它的成员,类型层面也能引用它:

enum LogLevel {
  Debug = 'DEBUG',
  Info = 'INFO',
  Warn = 'WARN',
  Error = 'ERROR',
}

// 运行时:枚举是真实对象
console.log(LogLevel.Debug); // "DEBUG"
console.log(Object.values(LogLevel)); // ["DEBUG", "INFO", "WARN", "ERROR"]

// 类型层面:可以用成员做类型标注
function log(level: LogLevel, message: string) { /* ... */ }

// 双向映射(仅数字枚举)
enum Num { A, B }
console.log(Num[0]); // "A"

枚举的好处是开箱即用,不用写额外的推导代码。但它也有争议:字符串枚举没有反向映射,数字枚举的反向映射有时会造成意外行为;此外枚举是TypeScript对JavaScript的语法扩展,无法与某些构建工具或标准演进方向(如using声明、装饰器标准)完美贴合。所以不少团队规范里会倾向用as const对象替代枚举,这也是上面方案一流行起来的原因。

还有一种折中:const enum会在编译时把成员内联成字面量,不生成运行时对象,性能更好,但代价是失去了运行时访问能力,适合纯类型场景。要注意的是,在isolatedModules模式下,跨文件使用const enum会被禁止,实际项目中要先确认构建链路是否支持。

类型与值的判断技巧:什么时候该选哪种方案

选择方案前,先问自己一个问题:这块信息在运行时是否需要被读取?如果只是给编译器做检查用,比如约束函数参数、定义返回值结构,那就老老实实用type和interface,不要为了"看起来方便"引入多余的运行时对象。如果需要在运行时遍历、序列化、做穷尽校验,那就必须有真实值结构。

两者的判断可以用一个简单对照来总结:

场景推荐方案理由
纯类型约束,运行时不涉及type / interface零运行时开销,类型擦除后不占体积
需要遍历一组常量as const 对象或数组单一数据源,类型自动同步
需要语义化的命名空间enum官方支持,运行时可直接访问成员
对包体积敏感的常量const enum编译期内联,无运行时对象

另外一个进阶技巧是类与装饰器的组合。TypeScript 5.0之后标准的装饰器语法配合emitDecoratorMetadata,可以在运行时拿到类的构造参数类型等元数据,这也是NestJS等框架实现依赖注入的基础。不过这属于框架层面的元编程,普通业务代码中用前面几个方案已经足够。

最后要提醒的是,即使引入了运行时结构,类型安全依然不能替代运行时校验。来自接口请求、用户输入的数据,编译器无法保证其形状,这类场景应该配合zod、valibot这类库,用一份schema同时生成类型和运行时校验逻辑,真正做到类型与值的边界双向打通。

TypeScript类型字面量类型类型与运行时边界修改时间:2026-09-10 22:24:54

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260910/54306.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。