在 TypeScript 日常开发中,我们常常会遇到这样一种场景:在定义参数类型的时候,我们会希望参数类型更加具体,而不是更宽泛 string
, number
或 boolean
。
比如,将 method
的类型定义为 "GET" | "POST"
,可以限制这个参数仅为固定的字符串 "GET"
和 "POST"
,而不是任意字符串。相比于仅将参数定义为 string
, 如果传入参数不正确,TS 会在编译时就抛出错误,从而免去了添加手动校验参数的逻辑。
这里的 "GET"
和 "POST"
称为字面量类型 (Literal Type)。将多个字面量类型通过 |
连接起来就组成了联合字面量类型,这种技巧在 TS 类型定义中非常常见。
然而,这种类型定义方式会造成一些“反直觉”的问题:
// 定义函数签名,method 的类型为联合字面量类型而不是 string
declare function handleRequest(url: string, method: "GET" | "POST"): void;
// 构造请求体,此处的 method 会被 TS 推断为 string 类型
const req = { url: "https://example.com", method: "GET" };
// ❌ 报错:Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.
handleRequest(req.url, req.method);
将字符串 "GET"
传递给类型限制为 "GET" | "POST"
的参数时不被 TS 允许。TS 认为种情况是一种错误,因为 req.method
被推断为 string
,而不是 "GET"
。
解决这种宽泛的原始类型和收窄的字面量类型不匹配的方法有以下几种
方法 1 : 添加类型断言
通过 as
类型断言,让 TS 知道此处的 req.method
的具体类型。可以在构造对象或者在调用对象时断言:
// 构造时断言
const req = { url: "https://example.com", method: "GET" as "GET" };
// 调用时断言
handleRequest(req.url, req.method as "GET");
也可以显式为 req
对象声明类型:
// 显示声明类型
type GetRequest = {
url: string;
method: "GET";
};
const req: GetRequest = { url: "https://example.com", method: "GET" };
handleRequest(req.url, req.method);
方法 2 : as const
as const
允许 TS 保留字面量类型,同时会为每个字段的类型加上修饰符 readonly
。
The
as const
suffix acts likeconst
but for the type system, ensuring that all properties are assigned the literal type instead of a more general version likestring
ornumber
.
as const
后缀在类型系统中类似于const
,确保所有属性都被分配为字面类型,而不是更通用的类型,如string
或number
。
const req = { url: "https://example.com", method: "GET" } as const; // ✅ 此时 req.method 的类型时 "GET" 而不是 string
此时 req.method
的类型时 "GET"
而不是 string
:
方法 3 : 抽离枚举 enum
一般采用联合字面量类型的地方都可以改用枚举类型。在类型定义时使用枚举类型,在消费该类型的地方使用枚举值,就不会有类型错误。
enum Method {
GET = "GET",
POST = "POST",
};
// 构造和调用使用同一套类型
declare function handleRequest(url: string, method: Method): void;
const req = { url: "https://example.com", method: Method.GET };