美文网首页
TypeScript 详解之 TypeScript 的类型断言

TypeScript 详解之 TypeScript 的类型断言

作者: you的日常 | 来源:发表于2023-08-10 20:35 被阅读0次

简介

对于没有类型声明的值,TypeScript 会进行类型推断,很多时候得到的结果,未必是开发者想要的。

type T = 'a'|'b'|'c';
let foo = 'a';

let bar:T = foo; // 报错

上面示例中,最后一行报错,原因是 TypeScript 推断变量foo的类型是string,而变量bar的类型是'a'|'b'|'c',前者是后者的父类型。父类型不能赋值给子类型,所以就报错了。

TypeScript 提供了“类型断言”这样一种手段,允许开发者在代码中“断言”某个值的类型,告诉编译器此处的值是什么类型。TypeScript 一旦发现存在类型断言,就不再对该值进行类型推断,而是直接采用断言给出的类型。

这种做法的实质是,允许开发者在某个位置“绕过”编译器的类型推断,让本来通不过类型检查的代码能够通过,避免编译器报错。这样虽然削弱了 TypeScript 类型系统的严格性,但是为开发者带来了方便,毕竟开发者比编译器更了解自己的代码。

回到上面的例子,解决方法就是进行类型断言,在赋值时断言变量foo的类型。

type T = 'a'|'b'|'c';

let foo = 'a';
let bar:T = foo as T; // 正确

上面示例中,最后一行的foo as T表示告诉编译器,变量foo的类型断言为T,所以这一行不再需要类型推断了,编译器直接把foo的类型当作T,就不会报错了。

总之,类型断言并不是真的改变一个值的类型,而是提示编译器,应该如何处理这个值。

类型断言有两种语法。

// 语法一:<类型>值
<Type>value

// 语法二:值 as 类型
value as Type

上面两种语法是等价的,value表示值,Type表示类型。早期只有语法一,后来因为 TypeScript 开始支持 React 的 JSX 语法(尖括号表示 HTML 元素),为了避免两者冲突,就引入了语法二。目前,推荐使用语法二。

// 语法一
let bar:T = <T>foo;

// 语法二
let bar:T = foo as T;

上面示例是两种类型断言的语法,其中的语法一因为跟 JSX 语法冲突,使用时必须关闭 TypeScript 的 React 支持,否则会无法识别。由于这个原因,现在一般都使用语法二。

下面看一个例子。对象类型有严格字面量检查,如果存在额外的属性会报错。

// 报错
const p:{ x: number } = { x: 0, y: 0 };

上面示例中,等号右侧是一个对象字面量,多出了属性y,导致报错。解决方法就是使用类型断言,可以用两种不同的断言。

// 正确
const p0:{ x: number } =
  { x: 0, y: 0 } as { x: number };

// 正确
const p1:{ x: number } =
  { x: 0, y: 0 } as { x: number; y: number };

上面示例中,两种类型断言都是正确的。第一种断言将类型改成与等号左边一致,第二种断言使得等号右边的类型是左边类型的子类型,子类型可以赋值给父类型,同时因为存在类型断言,就没有严格字面量检查了,所以不报错。

下面是一个网页编程的实际例子。

const username = document.getElementById('username');

if (username) {
  (username as HTMLInputElement).value; // 正确
}

上面示例中,变量username的类型是HTMLElement | null,排除了null的情况以后,HTMLElement 类型是没有value属性的。如果username是一个输入框,那么就可以通过类型断言,将它的类型改成HTMLInputElement,就可以读取value属性。

注意,上例的类型断言的圆括号是必需的,否则username会被断言成HTMLInputElement.value,从而报错。

类型断言不应滥用,因为它改变了 TypeScript 的类型检查,很可能埋下错误的隐患。

const data:object = {
  a: 1,
  b: 2,
  c: 3
};

data.length; // 报错

(data as Array<string>).length; // 正确

上面示例中,变量data是一个对象,没有length属性。但是通过类型断言,可以将它的类型断言为数组,这样使用length属性就能通过类型检查。但是,编译后的代码在运行时依然会报错,所以类型断言可以让错误的代码通过编译。

类型断言的一大用处是,指定 unknown 类型的变量的具体类型。

const value:unknown = 'Hello World';

const s1:string = value; // 报错
const s2:string = value as string; // 正确

上面示例中,unknown 类型的变量value不能直接赋值给其他类型的变量,但是可以将它断言为其他类型,这样就可以赋值给别的变量了。

另外,类型断言也适合指定联合类型的值的具体类型。

const s1:number|string = 'hello';
const s2:number = s1 as number;

上面示例中,变量s1是联合类型,可以断言其为联合类型里面的一种具体类型,再将其赋值给变量s2

类型断言的条件

类型断言并不意味着,可以把某个值断言为任意类型。

const n = 1;
const m:string = n as string; // 报错

上面示例中,变量n是数值,无法把它断言成字符串,TypeScript 会报错。

类型断言的使用前提是,值的实际类型与断言的类型必须满足一个条件。

expr as T

上面代码中,expr是实际的值,T是类型断言,它们必须满足下面的条件:exprT的子类型,或者Texpr的子类型。

也就是说,类型断言要求实际的类型与断言的类型兼容,实际类型可以断言为一个更加宽泛的类型(父类型),也可以断言为一个更加精确的类型(子类型),但不能断言为一个完全无关的类型。

但是,如果真的要断言成一个完全无关的类型,也是可以做到的。那就是连续进行两次类型断言,先断言成 unknown 类型或 any 类型,然后再断言为目标类型。因为any类型和unknown类型是所有其他类型的父类型,所以可以作为两种完全无关的类型的中介。

// 或者写成 <T><unknown>expr
expr as unknown as T

上面代码中,expr连续进行了两次类型断言,第一次断言为unknown类型,第二次断言为T类型。这样的话,expr就可以断言成任意类型T,而不报错。

下面是本小节开头那个例子的改写。

const n = 1;
const m:string = n as unknown as string; // 正确

上面示例中,通过两次类型断言,变量n的类型就从数值,变成了完全无关的字符串,从而赋值时不会报错。

as const 断言

如果没有声明变量类型,let 命令声明的变量,会被类型推断为 TypeScript 内置的基本类型之一;const 命令声明的变量,则被推断为值类型常量。

// 类型推断为基本类型 string
let s1 = 'JavaScript';

// 类型推断为字符串 “JavaScript”
const s2 = 'JavaScript';

上面示例中,变量s1的类型被推断为string,变量s2的类型推断为值类型JavaScript。后者是前者的子类型,相当于 const 命令有更强的限定作用,可以缩小变量的类型范围。

有些时候,let 变量会出现一些意想不到的报错,变更成 const 变量就能消除报错。

let s = 'JavaScript';

type Lang =
  |'JavaScript'
  |'TypeScript'
  |'Python';

function setLang(language:Lang) {
  /* ... */
}

setLang(s); // 报错

上面示例中,最后一行报错,原因是函数setLang()的参数language类型是Lang,这是一个联合类型。但是,传入的字符串s的类型被推断为string,属于Lang的父类型。父类型不能替代子类型,导致报错。

一种解决方法就是把 let 命令改成 const 命令。

const s = 'JavaScript';

这样的话,变量s的类型就是值类型JavaScript,它是联合类型Lang的子类型,传入函数setLang()就不会报错。

另一种解决方法是使用类型断言。TypeScript 提供了一种特殊的类型断言as const,用于告诉编译器,推断类型时,可以将这个值推断为常量,即把 let 变量断言为 const 变量,从而把内置的基本类型变更为值类型。

let s = 'JavaScript' as const;
setLang(s);  // 正确

上面示例中,变量s虽然是用 let 命令声明的,但是使用了as const断言以后,就等同于是用 const 命令声明的,变量s的类型会被推断为值类型JavaScript

使用了as const断言以后,let 变量就不能再改变值了。

let s = 'JavaScript' as const;
s = 'Python'; // 报错

上面示例中,let 命令声明的变量s,使用as const断言以后,就不能改变值了,否则报错。

注意,as const断言只能用于字面量,不能用于变量。

let s = 'JavaScript';
setLang(s as const); // 报错

上面示例中,as const断言用于变量s,就报错了。下面的写法可以更清晰地看出这一点。

let s1 = 'JavaScript';
let s2 = s1 as const; // 报错

另外,as const也不能用于表达式。

let s = ('Java' + 'Script') as const; // 报错

上面示例中,as const用于表达式,导致报错。

as const也可以写成前置的形式。

// 后置形式
expr as const

// 前置形式
<const>expr

as const断言可以用于整个对象,也可以用于对象的单个属性,这时它的类型缩小效果是不一样的。

const v1 = {
  x: 1,
  y: 2,
}; // 类型是 { x: number; y: number; }

const v2 = {
  x: 1 as const,
  y: 2,
}; // 类型是 { x: 1; y: number; }

const v3 = {
  x: 1,
  y: 2,
} as const; // 类型是 { readonly x: 1; readonly y: 2; }

上面示例中,第二种写法是对属性x缩小类型,第三种写法是对整个对象缩小类型。

总之,as const会将字面量的类型断言为不可变类型,缩小成 TypeScript 允许的最小类型。

下面是数组的例子。

// a1 的类型推断为 number[]
const a1 = [1, 2, 3];

// a2 的类型推断为 readonly [1, 2, 3]
const a2 = [1, 2, 3] as const;

上面示例中,数组字面量使用as const断言后,类型推断就变成了只读元组。

由于as const会将数组变成只读元组,所以很适合用于函数的 rest 参数。

function add(x:number, y:number) {
  return x + y;
}

const nums = [1, 2];
const total = add(...nums); // 报错

上面示例中,变量nums的类型推断为number[],导致使用扩展运算符...传入函数add()会报错,因为add()只能接受两个参数,而...nums并不能保证参数的个数。

事实上,对于固定参数个数的函数,如果传入的参数包含扩展运算符,那么扩展运算符只能用于元组。只有当函数定义使用了 rest 参数,扩展运算符才能用于数组。

相关文章

  • ES6 之后 typescript 类

    1.0 typescript 类型断言 letsomeValue:any="this is a string";l...

  • Typescript 中的 const 断言

    在 Typescript 4.3 中,TypeScript 引入了 const 断言。const 断言用于告诉 T...

  • TypeScript 类型断言

    类型断言(Type Assertion)可以用来手动指定一个值的类型。 语法 <类型>值 或者 值 as 类...

  • typescript 类型断言

    有时候会遇到这样的情况,你会比TypeScript更了解某个值的详细信息。 通常这会发生在你清楚地知道一个实体具有...

  • TypeScript类型断言

    类型断言 类型断言(Type Assertion)可以用来手动指定一个值的类型。 语法 在 tsx 语法(Reac...

  • TypeScript 类型断言

    语法 <类型>值 或者 值 as 类型 在 tsx 语法(React 的 jsx 语法的 ts 版)中必须用后一种...

  • TypeScript类型断言

    类型断言(Type Assertion)可以用来手动指定一个值的类型 自己理解,在联合类型的时候,只能访问联合类型...

  • Typescript类型断言

    语法 <类型>值 或者 值 as 类型 在 tsx 语法(React 的 jsx 语法的 ts 版)中必须用后一种...

  • typescript类型断言

    类型断言 有时候你会遇到这样的情况,你会比TypeScript更了解某个值的详细信息。 通常这会发生在你清楚地知道...

  • Typescript类型断言

    类型断言 TS 类型断言定义:把两种 有重叠关系的 数据类型 进行相互转换,把其中一种数据类型 转换成 另外一种数...

网友评论

      本文标题:TypeScript 详解之 TypeScript 的类型断言

      本文链接:https://www.haomeiwen.com/subject/ansnpdtx.html