先记住这个答案
TypeScript 5.0 引入 const 类型参数,语法是在类型参数前写 const,例如 <const T extends readonly string[]>。它让符合条件的调用处字面量优先保留较窄信息,适合路由表和事件名列表。约束与编译器版本都会影响结果:本批使用的 TypeScript 5.9.3 中,直接数组字面量在 readonly 约束下得到只读元组,在可变约束下也能保留可变元组,不能机械套用旧示例中的宽数组回退结论。已经拓宽的 string[] 变量仍不会恢复原始名单。这个修饰符也不会冻结对象。
- 正确语法是在类型参数前写 const
- 约束与编译器版本共同影响元组可变性
- 已拓宽变量和运行时冻结不由该修饰符解决
为什么 API 作者会需要这个修饰符
配置辅助函数常希望知道用户提供了哪些具体名称,而不是只得到字符串数组。要求每个调用者记得追加 as const 容易遗漏,const 类型参数可以把合适的推断偏好放进函数签名,让常见字面量调用更自然地保留信息。
示例对比只读约束与可变约束,并加入已经声明为 string[] 的变量。当前编译器对两种直接字面量调用都保留元组,只读性质随约束不同;宽变量则仍是宽数组。应核对实际使用版本,不能把早期版本的回退示例当成所有版本永久不变的规则。
function names<const T extends readonly string[]>(value: T): T { return value; }
function mutableNames<const T extends string[]>(value: T): T { return value; }
const exact = names(['open', 'close']);
const mutable = mutableNames(['open', 'close']);
const stored: string[] = ['open', 'close'];
const widened = names(stored);在 TypeScript 5.9.3 中,exact 是 readonly ["open", "close"],mutable 是可变的 ["open", "close"],widened 仍为 string[]。函数体返回原引用,静态推断没有在运行时复制或冻结输入。
只读约束与实际写入责任怎样配合
如果函数只观察配置,接受 readonly 数组能同时兼容可变数组和只读元组,通常比要求调用方交出写入权限更贴合需求。如果函数确实要修改数组,就不能只为了保留窄推断而假装只读,应重新设计输入输出和可变性责任。
const 类型参数与调用方 as const 可以出现在不同边界:前者让 API 选择推断策略,后者由表达式作者声明字面量意图。两者都属于静态类型机制,不会代替 Object.freeze 或不可变数据结构,也不能阻止其他可写引用修改共享对象。
版本与已有变量为什么是边界
一个变量在调用前已被推断或注解成 string[],函数接收到的证据就是这个类型。即使运行时目前只有两个元素,编译器也不能据此保证以后永远没有第三种字符串。需要静态名单时,应在列表定义处保留元组或有限联合信息。
库发布包含 const 类型参数的声明时,旧编译器可能连语法都无法解析。应声明支持的 TypeScript 范围,并在消费端版本上验证声明产物。不要只看库自身编译成功,就认为所有下游工具链都能读取这份公开类型接口。
容易答错的地方
- 正确写法是 T extends const
- const 是类型参数声明上的修饰符,应该写在 T 前面;写成 T extends const 并不会启用这种推断,本例编译器会报告找不到 const 类型名称。
- 加 const 就能恢复任何变量的字面量集合
- 提前拓宽的信息不会凭空恢复,输入来源和约束都会限制推断;应在需要保留名单的声明边界就表达对应的静态契约。
面试官还会怎么问?
为什么只读约束能接受普通可变数组?
只读使用者只承诺观察,不要求获得写入能力,因此可变数组通常可以满足这种较小使用契约;反向把只读元组交给可变接口则不同。
const 类型参数会让所有调用结果都只读吗?
不会无条件改写已有输入类型,它主要影响合适的字面量推断;传入已经声明的可变变量时,仍应查看返回的实际类型。
需要真正冻结配置应该怎么做?
应在运行时执行符合对象层级的冻结或采用不可变更新策略,并明确共享引用边界;类型修饰符只能提供开发阶段的静态约束。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。