前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >TS之类型断言

TS之类型断言

原创
作者头像
Ashen
修改2020-07-02 14:30:06
1.2K0
修改2020-07-02 14:30:06
举报
文章被收录于专栏:Ashenの前端技术Ashenの前端技术

参考资料:https://ts.xcatliu.com/basics/type-assertion.html

最近在研究TypeScript,现在很多模块都是用这个写的,不懂也不方便看源码呐,所以随手做下笔记(我一直觉得写笔记是一个很好的学习方法,虽然会花费较长的时间,但写的过程却可以加深印象)。


类型断言用于手动指定一个值的类型。

一、语法

代码语言:txt
复制
值 as 类型

二、用途

2.1 将一个联合类型断言为其中一个类型

代码语言:javascript
复制
interface Cat {
    name:string;
    run():void;
}
interface Fish {
    name:string;
    swim():void;
}
function getName(animal:Cat|Fish):string{
    return animal.name;
}

只能访问联合属性中共有的属性和方法——name

如果在不确定类型的时候就想访问一个类型确定的属性和方法,就需要【断言】:

代码语言:javascript
复制
function isFish(animal:Cat|Fish):boolean{
    return (typeof (animal as Fish).swim) === "function";
}

将变量animal断言为Fish类型,那么访问其swim属性,就可以通过编译器的检查。

类型断言相当于欺骗编译器,编译的时候不报错,不代表运行的时候不报错。

代码语言:javascript
复制
const tom:Cat = {
    name:"cat",
    run(){
        console.log("running");
    }
}
function swim(animal:Cat|Fish){
    (animal as Fish).swim();
}
swim(tom);

编译结果:

代码语言:javascript
复制
var tom = {
    name: "cat",
    run: function () {
        console.log("running");
    }
};
function swim(animal) {
    animal.swim();
}
swim(tom);
//TypeError: animal.swim is not a function

2.2 将一个父类断言为更加具体的子类

代码语言:javascript
复制
class ApiError extends Error{
    code:number = 0;
}
class HttpError extends Error{
    statusCode:number = 200;
}
function isApiError(error:Error){
    return (typeof (error as ApiError).code === "number");
}

父类Error并没有code属性,会直接报错,所以需要将其断言为子类ApiError

上例用instanceof判断似乎更为合适,如果ApiErrorHttpError为接口,那么就只能用断言来判断了:

代码语言:javascript
复制
interface ApiError extends Error{
    code:number;
}
interface HttpError extends Error{
    statusCode:number;
}

2.3 将任何一个类型断言为any

代码语言:javascript
复制
window.a = "hello world";
//Property 'a' does not exist on type 'Window & typeof globalThis'

只是想给window添加一个属性,但因为window上并没有a属性,所以报错。此时,就需要将window断言为any

代码语言:javascript
复制
(window as any).a = "hello world";

不能滥用as any,也不能完全否定它的作用,需要在类型的严格性和开发的便利性之间掌握平衡。

2.4 将any断言为一个具体的类型

代码语言:javascript
复制
function getCacheData(key: string): any {
    return (window as any).cache[key];
}
interface Cat {
    name:string;
    run():void;
}
const tom = getCacheData("tom") as Cat;

getCacheData执行以后返回的是any类型,在赋值变量tom时候可以将其断言为Cat类型。后续对tom的访问时就有了代码补全,提高了代码的可维护性。

三、类型断言的限制

综上所述,类型断言有以下特点:

  1. 联合类型可以被断言为其中一个类型
  2. 父类可以被断言为子类
  3. 任何类型都可以被断言为any
  4. any可以被断言为任何类型

但类型之间的断言却是有限制的。若A能兼容B,那么可以将A断言为B,也可以将B断言为A。

代码语言:javascript
复制
interface Animal {
    name:string;
}
interface Cat {
    name:string;
    run():void;
}
const tom:Cat = {
    name:"tom",
    run(){
        console.log("running");
    }
}
const animal:Animal = tom;

TS是结构类型系统,类型之间的对比只会比较它们最终的结构,而会忽略它们定义时的关系。所以在上例中,尽管AnimalCat定义时并没有任何关系,但结构上可以理解为Cat继承于Animal

代码语言:javascript
复制
interface Cat extends Animal{
    run():void;
}

即:Animal兼容Cat。此时二者可以互相断言:

代码语言:javascript
复制
function testAnimal(animal:Animal){
    return animal as Cat
}
function testCat(cat:Cat){
    return cat as Animal;
}
  • 允许 animal as cat 是因为父类可以被断言为子类。
  • 允许 cat as animal 是因为子类拥有父类的所有属性和方法,调用也不会出问题。

综上所述:

  1. 联合类型可以被断言为其中一个类型
  2. 父类可以被断言为子类
  3. 任何类型都可以被断言为any
  4. any可以被断言为任何类型
  5. 要使得A能够被断言为B,需要A兼容B或B兼容A

前四种情况都是最后一种的特例。

四、双重断言

既然:

  1. 任何类型都可以被断言为any
  2. any可以被断言为任何类型

那么类型岂不可以互相断言了?

代码语言:javascript
复制
const a:number = 666;
const b:string = a as any as string;

但这么做反而会导致一系列问题,所以,不到万不得已,最好不要这么做。

五、类型断言 VS 类型转换

类型断言只会影响TS编译时的类型,类型断言语句会在编译结果中删除。

代码语言:javascript
复制
function toBoolean(something: any): boolean {
    return something as boolean;
}
toBoolean(1);
// 返回值为 1

而类型转换则会影响编译结果:

代码语言:javascript
复制
function toBoolean(something: any): boolean {
    return Boolean(something);
}
toBoolean(1);
// 返回值为 true

六、类型断言 VS 类型声明

类型断言:

代码语言:javascript
复制
function getCacheData(key: string): any {
    return (window as any).cache[key];
}
interface Cat {
    name: string;
    run(): void;
}
const tom = getCacheData('tom') as Cat;

类型申明:

代码语言:javascript
复制
const tom:Cat = getCacheData('tom');

因为getCacheData()的返回类型是any,所以这个例子二者效果是一样的。但二者还是有区别的:

代码语言:javascript
复制
interface Animal{
    name:string;
}
interface Cat {
    name:string;
    run():void;
}
const animal:Animal = {
    name:"tom"
}
const tom = animal as Cat;

但类型声明却不行:

代码语言:javascript
复制
const tom:Cat = animal;
//Property 'run' is missing in type 'Animal' but required in type 'Cat'

Animal兼容Cat,所以可以将animal断言为Cat,赋值给tom。但直接申明tomCat类型是不可以的。

深入的讲,它们的核心区别就在于:

  • animal 断言为 Cat,只需要满足 Animal 兼容 CatCat 兼容 Animal 即可
  • animal 赋值给 tom,需要满足 Cat 兼容 Animal 才行

但是 Cat 并不兼容 Animal


End~

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、语法
  • 二、用途
    • 2.1 将一个联合类型断言为其中一个类型
      • 2.2 将一个父类断言为更加具体的子类
        • 2.3 将任何一个类型断言为any
          • 2.4 将any断言为一个具体的类型
          • 三、类型断言的限制
          • 四、双重断言
          • 五、类型断言 VS 类型转换
          • 六、类型断言 VS 类型声明
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档