const 类型参数解决了什么推断问题?
简化版
const 类型参数让泛型函数在推断对象、数组、字面量时更倾向保留字面量类型,而不是过早拓宽成 string、number 或普通数组。
它适合库函数、路由配置、事件表、状态机配置等希望从输入值推导精确类型的场景,可以减少调用方手写 as const。
详细版
普通泛型可能拓宽:
function defineRoutes<T>(routes: T) {
return routes;
}
使用 const 类型参数:
function defineRoutes<const T>(routes: T) {
return routes;
}
const routes = defineRoutes([
{ path: "/home", name: "home" }
]);
推断会更保留 "/home"、"home" 这类字面量信息。它主要影响类型推断,不会改变运行时代码。
完整版教学
一、问题来自字面量拓宽
TypeScript 为了让变量可变,常把字面量拓宽。
let name = "home"; // string
const name2 = "home"; // "home"
在泛型函数里,对象和数组也可能被推得不够精确。
const 类型参数的目标是让库函数更容易保留调用方传入的字面量信息。
二、过去常用 as const
const routes = [
{ path: "/home", name: "home" }
] as const;
as const 会让字面量保持精确,并把对象属性和数组变得更只读。
这很有用,但要求调用方记得写。
三、const 类型参数把意图放进 API
function defineConfig<const T>(config: T): T {
return config;
}
调用方:
const config = defineConfig({
mode: "dark",
features: ["search", "share"]
});
函数签名表达了“请尽量保留字面量信息”。
四、适合配置型 API
常见场景:
- 路由表
- 菜单配置
- 事件名表
- 状态机
- schema 定义
- 组件变体配置
const events = defineEvents({
login: ["userId"],
logout: []
});
后续可以从 events 推导出 "login" | "logout"。
五、它不是运行时 const
const T 不会冻结对象,也不会阻止运行时代码修改。
function f<const T>(value: T) {
return value;
}
这里的 const 是类型参数修饰符,只影响推断策略。
| 能力 | 作用阶段 |
|---|---|
const x = ... | 运行时绑定不可重新赋值 |
as const | 类型断言,保留字面量并只读 |
const T | 泛型推断偏好 |
六、和 satisfies 配合
const config = defineConfig({
mode: "dark",
retries: 3
} satisfies {
mode: "dark" | "light";
retries: number;
});
satisfies 负责检查形状,const 类型参数负责尽量保留输入的精确信息。两者组合适合配置 DSL。
七、常见误区与追问
- 误区:const 类型参数会冻结对象。 它只影响类型推断,不影响运行时可变性。
- 误区:有了 const 类型参数就不需要 as const。 某些变量声明和深层只读场景仍可能需要。
- 误区:它适合所有泛型函数。 数据处理函数可能不需要保留过度精确的字面量。
- 误区:const T 和 const 变量是同一件事。 一个是类型参数修饰符,一个是 JavaScript 声明。
- 追问:为什么库 API 喜欢用 const 类型参数? 可以从配置对象推导更精确的联合类型。
- 追问:它和 satisfies 怎么配合?
satisfies检查约束,const T保留输入字面量。
八、加强记忆
记忆链路:
caller literal -> const generic -> precise inferred type
回答时强调它是 TypeScript 5.x 的类型推断能力,适合 defineConfig、defineRoutes 这类配置型 API,作用是减少调用方手写 as const。