类型断言

类型断言有如下案例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20

interface Cat {
    name: string;
    run(): void;
}

interface Fish {
    name: string;
    swim(): void;
}

function getName(animal: Cat | Fish): string {
    return animal.name;
}

function isFish(animal: Cat | Fish): boolean {
    // 我觉得这种写法有点怪,在java中,我们会直接判断animal是否是Fish的实例类型
    return typeof (animal as Fish).swim === 'function';
}

类型断言只能欺骗TypeScript编译器,无法避免运行时错误,滥用类型断言可能会导致运行时错误:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20

interface Cat {
    name: string;
    run(): void;
}
interface Fish {
    name: string;
    swim(): void;
}

function swim(animal: Cat | Fish) {
    (animal as Fish).swim();
}

const tom: Cat = {
    name: 'Tom',
    run() { console.log('run') }
};
swim(tom);

类型断言也用于继承关系:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13

class ApiError extends Error {
    code: number = 0;
}

class HttpError extends Error {
    statusCode: number = 200;
}

function isApiError(error: Error): boolean {
    return typeof (error as ApiError).code === 'number';
}

什么时候使用instanceof

如果ApiError和HttpError是一个类,则使用instanceof更合适:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13

class ApiError extends Error {
    code: number = 0;
}

class HttpError extends Error {
    statusCode: number = 200;
}

function isApiError(error: Error): boolean {
    return error instanceof ApiError;
}

如果ApiError和HttpError不是一个真正的类,而是一个TypeScript接口。因为接口是一个类型,不是一个真正的值,它在编译结果中会被删除,所以就无法使用instanceof来做运行时判断了:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14

interface ApiError extends Error {
    code: number;
}

interface HttpError extends Error {
    statusCode: number;
}

function isApiError(error: Error) {
    // 编译就开始报错了
    return error instanceof ApiError;
}

这个时候就使用通过类型断言,判断是否存在code属性,来判断传入的参数是不是ApiError了。

什么时候断言为any

例如我们需要给window上加一个属性:

1
2
3

(window as any).foo = 1;

这种写法有可能掩盖了真正的类型错误,所以如果不是非常确定,就不要使用as any。上面的例子可以拖过扩展Windows类型来解决(我对这部分非常的陌生)。

类型断言与类型声明

两种写法如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18

function getCacheData(key: string): any {
    return (window as any).cache[key];
}

interface Cat {
    name: string;
    run(): void;
}

// 类型断言
const tom = getCacheData('tom') as Cat;
tom.run();

// 类型声明
const tom: Cat = getCacheData('tom');
tom.run();

理解这种差距,可以通过如下这个案例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19

interface Animal {
    name: string;
}
interface Cat {
    name: string;
    run(): void;
}

const animal: Animal = {
    name: 'tom'
};

// 编译通过
let tom = animal as Cat;

// 编译不通过
let tom: Cat = animal;

深入理解(我还是理解不了):

  • animal断言为Cat,只需要满足Animal兼容Cat或Cat兼容Animal即可
  • animal赋值为tom,需要满足Cat兼容Animal才行,但是案例中Cat并不兼容Animal

类型声明比类型断言更加严格,且比类型断言的as语法更优雅。

类型断言与泛型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13


function getCacheData(key: string): any {
    return (window as any).cache[key];
}

interface Cat {
    name: string;
    run(): void;
}

const tom = getCacheData<Cat>('tom')

泛型是最优的解决方案。