类型断言(as
),是一种编译时的机制,它不会在运行时检查类型,而是告诉编译器按照指定的类型处理变量。
expr as T
类型断言的前提:expr
是 T
的子类型,或者 T
是 expr
的子类型。
打破必须兼容的前提:可以通过先断言成 unknown 类型或 any 类型,然后再断言为目标类型。
expr as unknown as T
对于没有类型声明的值,TypeScript 会进行类型推断。
type T = 'ligang' | '李刚';
let myName = 'ligang';
let n: T = myName; // 不能将类型“string”分配给类型“T”
TypeScript 推断变量 myName
的类型是 string
,而变量 n
的类型是 ligang' | '李刚'
。上述会报错。
此时,可以使用类型断言,告诉编译器此处的值是什么类型。TypeScript 一旦发现存在类型断言,就不再进行类型推断,而是直接采用断言给出的类型。
let n: T = myName as T; // ✔️
const username = document.getElementById("username"); // HTMLElement | null
if (username) { // 排除null
username.value; // 类型“HTMLElement”上不存在属性“value”
}
as HTMLInputElement
来告诉 TypeScript 编译器, username
是 HTMLInputElement
类型的一个实例;从而可以访问 HTMLInputElement
上特有的属性 value
。
(username as HTMLInputElement).value // ✔️
HTMLInputElement
是HTMLElement
的一个子类,代表了 HTML 中的输入元素。
unknown
类型指定 unknown 类型的变量的具体类型。
let value: unknown = "Hello";
let len: number = value.length; // “value”的类型为“未知”
在上面的例子中,类型断言 value as string
告诉编译器将 value
当作字符串处理,从而允许访问 .length
属性。
let len: number = (value as string).length; // ✔️
⚠️ 注意:类型断言可以让错误的代码通过编译,但在运行时可能会报错。
如上述 value
实际值为 null
或 undefined
,运行时就会抛出错误。
TypeError: Cannot read properties of null (reading 'length')
类型守卫(is) 是一种运行时的机制,它们通过检查来确保变量的类型,并根据检查结果改变类型信息。
上一篇:TypeScript系列:第四篇 - typeof 与 keyof 中有提及
通过缩小类型,可以确保代码块中安全地使用变量。
function isNumber(x: unknown): x is number {
return typeof x === 'number';
}
在上述的例子中,isNumber
函数是一个用户自定义的类型守卫。它在运行时检查 value
是否为数值,并返回一个布尔值。
let value: unknown = 123;
if (isNumber(value)) {
console.log(value.toFixed(2));
}
如果 isNumber
返回 true
,则可以安全地调用 .toFixed()
方法。
类型守卫有几种形式,包括:
typeof
守卫:检查一个变量是否为特定的原始类型;instanceof
守卫:检查一个变量是否为特定类的实例;in
守卫:检查一个变量是否具有特定的属性;typescript 4.9 中添加了该运算符。
解决问题: 希望确保匹配某些类型,但也希望保留该表达式的最特定类型,以便进行推断。
与 as
不同,satisfies
不会改变变量的静态类型,因此在编译时不会影响类型检查的结果。
type Colors = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];
const palette: Record<Colors, string | RGB> = {
red: [255, 0, 0],
green: "#00ff00",
blue: [0, 0, 255]
};
const greenNormalized = palette.green.toUpperCase(); // 类型“string | RGB”上不存在属性“toUpperCase”。
上述丢失了每个属性的特定类型,如 green: "#00ff00"
是 string
类型,而通过上述方式变为了 string | RGB
。
const palette = {
red: [255, 0, 0],
green: '#00ff00',
blue: [0, 0, 255],
} satisfies Record<Colors, string | RGB>;
satisfies
满足可以验证表达式的类型是否匹配某种类型,而不需要更改该表达式的结果类型。
as const
会将字面量的类型断言为不可变类型,缩小成 TypeScript 允许的最小类型。
表达式 | 说明 |
---|---|
let s1 = 'JavaScript' | 类型推断为基本类型 string |
const s2 = 'JavaScript' | 类型推断为值类型:字符串 “JavaScript” |
let s3 = 'JavaScript' as const | 类型推断为值类型:字符串 “JavaScript” 【等同于上述 s2】 |
⚠️ as const
断言只能用于字面量,不能用于变量!
as const
断言可以用于整个对象,也可以用于对象的单个属性。
const o1 = {
a: 1 as const,
b: 2,
}; // 类型是 { a: 1; b: number; }
const o2 = {
a: 1,
b: 2,
} as const; // 类型是 { readonly a: 1; readonly b: 2; }
数组字面量使用 as const
断言后,类型推断就变成了只读元组。
// 类型推断为 number[]
const ary1 = [1, 2, 3];
// 类型推断为 readonly [1, 2, 3]
const ary2 = [1, 2, 3] as const;
示例:
function add(x:number, y:number) {
return x + y;
}
const num = [1, 2];
const total = add(...num); // 扩张参数必须具有元组类型或传递给 rest 参数
上面示例中,变量 num
的类型推断为 number[]
,导致使用扩展运算符...
传入函数add()
会报错,因为add()
只能接受两个参数,而 ...num
并不能保证参数的个数。
const num = [1, 2] as const;
const total = add(...num);
enum Foo {
X,
Y,
}
let e1 = Foo.X; // Foo
let e2 = Foo.X as const; // Foo.X
变量e1
的类型被推断为整个 Enum 类型;使用了as const
断言以后,变量e2
的类型被推断为 Enum 的某个成员,这意味着它不能变更为其他成员。
对于那些可能为空的变量(即可能等于undefined
或null
),TypeScript 提供了非空断言,保证这些变量不会为空,写法是在变量名后面加上感叹号!
。
x!.toFixed() // x不为空
在编写 TypeScript 代码时,推荐尽可能使用类型守卫,因为它们提供了运行时的安全性。类型断言应该谨慎使用,只在你完全确定变量类型的情况下使用,以避免运行时错误。