Skip to main content

Type Assertion

类型断言

从程序员的角度出发,告诉TypeScript类型的结果,相当于类型强制转换,以此来指定更具体的类型,无论其推断结果如何。类型断言编译后会被移除,并不会影响代码的运行时行为。

例如,使用document.getElementById方法的时候,TypeScript只知道它会返回HTMLElement类型的某种元素,并不知道其具体的类型,但是你可能知道页面上始终有一个具有特定 IDHTMLCanvasElement,此时可使用类型断言对类型进行约束。

简单的讲,类型断言是将一个数据指定为一个类型,不是通过数据进行类型推断分配给另一个类型。

TypeScript中,类型推断有两种语法:

  • 尖括号语法:<T>value
  • as语法:value as T
// <T>value
const canvas = <HTMLCanvasElement>document.getElementById("canvas");

// value as T
const canvas = document.getElementById("canvas") as HTMLCanvasElement;

TypeScript仅允许将类型断言转换为更具体或更不具体的类型版本。这个规则防止了一些可能存在问题的强制类型转换,比如:

// Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other.
// If this was intentional, convert the expression to 'unknown' first.
const x = "hello" as number;

as const

as const是TypeScript中的一种类型断言,它允许断言表达式具有特定的类型,并将其值视为只读值。使用as const可以使TypeScript为常量推断出更精确的类型,这可以导致代码中的类型检查和类型推断更加准确。当使用const断言构造新的文字表达式时,可以向语言本身传递一种信号,表明:

  • 在断言表达式中,不应扩展任何文字类型。
  • 对象字面量属性只读。
  • 数组字面量变成只读元组。
let x = 'hello' as const
let y = [10, 20] as const
let z = { text: 'hello' } as const

这个特性意味着那些原本只用于向编译器提示不可变性的数据类型通常可以省略。

function getShapes() {
let result = [
{ kind: 'circle', radius: 100 },
{ kind: 'square', sideLength: 50 },
] as const;

return result
}

此外,如果选择不使用TypeScript的枚举构造方法,甚至可以将此方法用于纯JavaScript代码中类似枚举的模式:

export const colors = {
red: 'red',
blue: 'blue',
green: 'green',
} as const

非空断言

程序员告诉TypeScript这个表达式的结果一定不为空(nullundefined),一般针对联合类型。

非空断言操作符!是一种类型断言,它告诉编译器一个值永远不会是nullundefined。操作x!会产生一个不包括nullundefined 类型的x的值。非空断言会在最终生成的JavaScript代码中被移除,但当值确实为null或者undefined的时候,仍然可能导致一些运行时错误,因此使用时需谨慎。

let name: string | null = null;

// we use the non-null assertion operator to tell the compiler that name will never be null
let nameLength = name!.length;

?.语法表示有值的为空,没值的时候为undefined,

const arr = [
{
id: 1,
name: 'zs',
},
{
id: 2,
name: 'ww'
}
]

const tar = arr.find((item) => item.id === 3)

console.log(tar?.name);
/**
相当于:
if (tar) {
console.log(tar.name)
} else {
console.log(undefined)
}
*/

satisfies

TypeScript开发者经常面临一个困境:希望确保某个表达式与某个类型匹配,但同时也希望保留该表达式的最具体类型以进行推断。

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]
};

// Property 'map' does not exist on type 'string'
const redComponent = palette.red.map((item) => item - 10)

// Property 'toUpperCase' does not exist on type 'RGB'.
const greenComponent = palette.green.toUpperCase()

如上示例,不能同时满足字符串和数组的需要,需要进行类型窄化后才能正常使用。

新的satisfies运算符允许验证表达式的类型是否与某个类型匹配,而不改变该表达式的结果类型。例如,可以使用satisfies运算符验证palette的所有属性是否与string | number[]兼容:

type Colors = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];

type ColorType = Record<Colors, string | RGB>
/**
type ColorType = {
red: string | RGB;
green: string | RGB;
blue: string | RGB;
}
*/

const palette = {
red: [255, 0, 0],
green: "#00ff00",
blue: [0, 0, 255]
} satisfies ColorType;

// It's OK.
const redComponent = palette.red.map((item) => item - 10)
const greenComponent = palette.green.toUpperCase()

satisfies也可以用来捕获许多可能的错误。如:可以确保一个对象具有某个类型的所有键,但不多于这些键:

type Colors = "red" | "green" | "blue";
const favoriteColors = {
"red": "yes",
"green": false,
"blue": "kinda",
// error
"platypus": false
} satisfies Record<Colors, unknown>;

也许我们并不关心属性名称是否匹配,但我们确实关心每个属性的类型。在这种情况下,我们还可以确保对象的所有属性值都符合某个类型:

type RGB = [red: number, green: number, blue: number];
const palette = {
red: [255, 0, 0],
green: "#00ff00",
// Source has 2 element(s) but target requires 3
blue: [0, 0]
} satisfies Record<string, string | RGB>;

注意点

  1. 使用断言时一定要100%确保当前表达式能返回类型准确的值。
  2. 对确定类型的值不能直接进行符合当前值类型的断言,如果确实需要这样的操作,必须让TypeScript不知道这个类型。
// error
let a = 1 as string

双重断言

由于类型断言规则过于保守,有时可能会禁止一些可能是有效的更复杂的强制类型转换。如果遇到这种情况,可进行两次类型断言:

  • 先转为anyunknown
  • 之后转为目标类型

任意类型和未知类型都可以被列席断言为其他类型。

const targetValue = (value as unknown) as T;

const x = ("hello" as any) as number;
const a = (1 as unknown) as string