字面量类型(Literal Types)
除了通用的 string 和 number 类型,我们还可以在类型位置上引用特定的字符串和数字。
可以这样理解:JavaScript 提供了不同的方式来声明变量,var 和 let 允许更改变量的值,而 const 则不允许。这种行为在 TypeScript 处理字面量类型时得到了体现。
ts
let changingString = "Hello World";
changingString = "Olá Mundo";
// 由于 `changingString` 可以表示任何可能的字符串,
// 因此 TypeScript 在类型系统中将其描述为 string 类型
// changingString: string
const constantString = "Hello World";
// 由于 `constantString` 只能表示唯一的字符串 "Hello World",
// 因此它具有字面量类型的表示
// const constantString: "Hello World";
单独使用时,字面量类型并没有太大价值:
ts
let x: "hello" = "hello";
// OK
x = "hello";
x = "howdy"; // ❌ 报错:类型 "howdy" 不能赋值给类型 "hello"
但如果将字面量类型组合成 联合类型(Union Types) ,就能表达更有意义的概念。例如,可以定义 仅接受特定值 的函数:
ts
function printText(text: "hello" | "goodbye") {
console.log(text);
}
printText("hello"); // ✅ OK
printText("goodbye"); // ✅ OK
printText("hi"); // ❌ 报错:类型 "hi" 不能赋值给类型 '"hello" | "goodbye"'
数值字面量类型的工作方式与字符串字面量类型相同:
ts
function rollDice(dice: 1 | 2 | 3 | 4 | 5 | 6) {
console.log(`Rolled a ${dice}`);
}
rollDice(3); // ✅ OK
rollDice(6); // ✅ OK
rollDice(7); // ❌ 报错:类型 "7" 不能赋值给类型 "1 | 2 | 3 | 4 | 5 | 6"
当然,你可以将字面量类型与非字面量类型结合使用:
ts
function sendMessage(message: string, priority: "low" | "medium" | "high") {
console.log(`Message: ${message}, Priority: ${priority}`);
}
sendMessage("System update available", "high"); // ✅ OK
sendMessage("Meeting at 3 PM", "medium"); // ✅ OK
sendMessage("Lunch time", "urgent"); // ❌ 报错:类型 "urgent" 不能赋值给类型 '"low" | "medium" | "high"'
还有一种字面量类型:布尔字面量类型(Boolean Literal Types) 。
布尔字面量类型只有两个可能的值,正如你所猜测的,它们分别是 true 和 false。实际上,boolean 类型只是 true | false 的一个 别名(alias) 。
ts
function toggle(value: true | false) {
return value ? "ON" : "OFF";
}
console.log(toggle(true)); // ✅ 输出: "ON"
console.log(toggle(false)); // ✅ 输出: "OFF"
let flag: boolean = true; // ✅ 这里的 boolean 其实是 true | false 的别名
flag = false; // ✅ 仍然合法
字面量推断(Literal Inference)
当你使用对象初始化一个变量时,TypeScript 默认假设 该对象的属性值可能会在之后被更改。例如,下面的代码:
ts
const obj = { counter: 0 }; // TypeScript 推断 obj.counter 的类型为 number
if (someCondition) {
obj.counter = 1; // ✅ 合法,因为 1 也是 number 类型
}
TypeScript 并不会将 1 赋值给一个之前为 0 的字段视为错误。换句话说,obj.counter 必须具有 number 类型,而不是 0 类型,因为类型不仅决定了读取行为,还决定了写入行为。
这种情况也同样适用于字符串类型:
ts
declare function handleRequest(url: string, method: "GET" | "POST"): void;
const req = { url: "https://example.com", method: "GET" };
handleRequest(req.url, req.method); // ❌ - Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.
在上面的例子中,req.method 被推断为 string 类型,而不是字面量类型 "GET"。这是因为在创建 req 和调用 handleRequest 之间,代码可能会修改 req.method 的值,比如将它赋值为 "GUESS"。因此,TypeScript 认为此代码存在错误。
有两种方法可以解决这个问题:
- 你可以通过在任意位置添加类型断言来改变推断:
ts
// 修改 1:
const req = { url: "https://example.com", method: "GET" as "GET" };
// 修改 2:
handleRequest(req.url, req.method as "GET");
修改 1 的意思是:"我打算让 req.method 永远保持字面量类型 "GET"",这会防止以后将 "GUESS" 等其他值赋给该字段。
修改 2 的意思是:"我知道由于其他原因,req.method 的值是 "GET""。这只是告诉 TypeScript,"尽管类型系统推断了它为其他类型,我确信它是 "GET"",因此不会进行进一步的类型检查。
- 你可以使用 as const 来将整个对象转换为字面量类型,从而确保 req 的所有属性都被推断为字面量类型,而不是更广泛的类型:
ts
const req = { url: "https://example.com", method: "GET" } as const;
handleRequest(req.url, req.method);
as const 后缀类似于 const,但它作用于 类型系统,确保所有属性被分配为字面量类型,而不是更一般的类型(如 string 或 number)。