TypeScript基础你不知道的JavaScript

探索 TypeScript 类型注解 - 类型检查

2020-01-01  本文已影响0人  zhilidali

Exploring TypeScript Type Annotations - Type Checking

本文由 WowBar 团队首发于 GitHub

作者: zhilidali

欢迎来到 《探索 TypeScript 类型注解》 系列教程。

上一篇介绍了如何创建自定义类型,
本篇深入探索 TypeScript 编译器的静态类型检查机制。

目录

正文

当数据类型操作不合理时,编译器静态编译时会提示错误。

let obj = { a: 1 };
// A. 编译器推断出 obj 的类型: let obj: { a: number; }

// B. 编译器检查数据的使用是否合理,不合理时会抛出 Error
obj.b; // Error: 属性 b 不存在
obj.a = '2'; // Error:'2' 不能赋值给 number 类型

通过上面编译器对类型注解的静态检查,可以初步了解到类型检查的机制:编译器会适当的推断数据的类型,以及检查类型的使用是否合理,下面我们对类型检查机制进一步探索。

类型推断

Type Inference 类型推断:当没有显式指定类型注解时,编译器会推断出一个类型。

基本类型推断

在定义变量、设置函数参数默认值、函数返回值时,编译器都会自动进行类型推断:

// 在变量初始化时推断
let foo;
// let foo: any
let bar = 'ts';
// let bar: string

// 推断函数参数和返回值类型
let a = (x = 1) => {}
// let a: (x?: number) => void

类型推断在一定程度上可以保持代码简洁易读,但有时并不总是如此

let foo;
let f = foo;
// let f: undefined

let b: string | number = 'TS';
let baz = b;
// let baz: string

最佳通用类型

从多个表达式的类型推断出最佳通用类型

let foo = ['ts', 1];
// let foo: (string | number)[]
// string | number 为联合类型,描述数据可以是其中的一种类型

上下文类型

由上下文推断出表达式的类型

let add: (a: number, b: number) => number;

add = function(x, y) {
    // 由 add 可推断出 x、y 以及返回值的类型
    return x + y;
};
// add = function(x: number, y: number): number {
//  return x + y;
// };

类型断言

Type Assertion 类型断言可以让你告诉编译器当前数据的类型,有两种语法:

interface Foo {
    foo: number;
}
let a = {};
// let a: {}
let b = {} as Foo;
// let b: Foo; 类型断言告诉编译器 b 的类型为 Foo

a.foo; // Error
(a as Foo).foo; // Ok
b.foo; // Ok

类型兼容

如果类型 Y 可以赋值给类型 X,即 X = Y,那么我们说,目标类型 X 兼容源类型 Y。

interface TypeA {
    a: string;
}
interface TypeB {
    a: string;
}

let foo: TypeA = {a: 'ts'}
// 基于结构类型,所以 foo 可赋值给 bar,即 TypeB 兼容 TypeA
let bar: TypeB = foo;

TS 基于结构子类型设计 (因为 JS 中广泛的使用匿名对象,例如函数表达式和对象字面量),与名义类型形成对比。
如上因为 TypeA 与 TypeB 结构相同 (属性及其类型),所以在结构类型系统中,它们是兼容的。
而在名义类型系统中,类型的兼容性或等价性是基于声明和名称来决定的。

基本类型兼容

之前“数据类型”篇幅中已有介绍,详情请点击此处

// `string` 兼容 `null`; `null` 是 `string` 的子类型
let str: string = null;

成员结构兼容

必选成员少的兼容成员多的,即源类型至少具有与目标类型相同的成员

基本结构兼容

let foo: { a: string } = { a: 'a' };
let bar: { a: string, b: string } = { a: 'a', b: 'b' };

foo = bar; // OK, 少兼容多
bar = foo; // Error
// 函数返回值
let foo = () => ({ a: 'a' });
let bar = () => ({ a: 'a', b: 'b' });

foo = bar; // Ok, 少兼容多
bar = foo; // Error

类兼容

类的实例成员少的兼容成员多的 (比较两个类的对象时,静态成员不比较)

class A {
    foo: number = 1
}
class B {
    foo: number = 2
    bar: number = 3
}
class C extends A {
    baz: number = 4;
}

let a = new A();
let b = new B();
let c = new C();

a = b; // Ok, 少兼容多
b = a; // Error

c = a; // Error
a = c; // Ok, 少兼容多

枚举兼容

枚举类型与数值类型相互兼容,枚举之间不兼容

enum E { A, B }
enum F { X, Y }

// 枚举类型与数值类型相互兼容
let foo: E = 123; // OK, 枚举类型兼容数值类型
let bar: number = E.A; // OK, 数值类型兼容枚举类型
// 枚举之间不兼容
let baz = E.A;
baz = F.X; // Error

泛型兼容

// 类型成员都为空,相互兼容
interface Empty<T> {}
let x: Empty<number> = {};
let y: Empty<string> = {};

x = y; // OK
y = x; // OK
// 成员类型不同,所以相互不兼容
interface NotEmpty<T> {
    data: T;
}
let x: NotEmpty<number> = { data: 1 };
let y: NotEmpty<string> = { data: '1' };

x = y; // Error
y = x; // Error

函数参数个数兼容

多兼容少

必选参数

let foo = (a: number) => a;
let bar = (a: number, b: number) => a + b;

foo = bar; // Error, 当 strictFunctionTypes 为 true 时
bar = foo; // OK, 多兼容少

必选参数、可选参数、剩余参数

let a = (p1: number, p2: number) => { }
let b = (p1?: number, p2?: number) => { }
let c = (...args: number[]) => {}

a = b; // Ok
a = c; // Ok

b = a; // Error, 当 strictFunctionTypes 为 true 时
b = c; // Error, 当 strictFunctionTypes 为 true 时

c = a; // Ok
c = b; // Ok

命名参数

let foo = (p: { a: number }) => p.a;
let bar = (p: { a: number; b: number }) => p.a;

foo = bar; // Error when strictFunctionTypes is true
bar = foo; // OK

类型保护

许多表达式可以确保在某个作用域中运行时,数据有着更精确的类型,称为类型保护。

常识别为类型保护的操作符

function getLength(arg: string | null): number {
    arg.length; // Error,编译器检查到 arg 的类型为 string 或 null,所以不能访问 length 属性
    if (arg === null) {
        return 0; // 编译器可以推断出,在这个 if 子句中,arg 的类型是 null
    }
    return arg.length; // 编译器可以推断出,此处 arg 的类型是 string
}

类型谓词

函数的返回值类型为类型谓词 parameterName is Type, 其中 parameterName 为函数参数。

// arg is string 是类型谓词
function isString(arg: any): arg is string {
    return typeof arg === 'string';
}
function f(foo: string | string[]) {
    if (isString(foo)) {
        foo.toUpperCase(); // OK
    } else if (Array.isArray(foo)) {
        foo.map(it => it.toUpperCase()); // OK
    }
}

可识别为类型保护的关键字

如下三个 JS 关键字可以帮助 TS 进一步识别类型

function f(foo: string | string[]) {
    foo.toUpperCase(); // Error
    foo.map(it => it.toUpperCase());
    foo.toFixed(); // Error

    if (typeof foo === 'string') {
        foo.toUpperCase(); // OK
    } else if (foo instanceof Array) {
        foo.map(it => it.toUpperCase()); // OK
    }
}
function b(bar: { a: string } | { b: string }) {
    bar.a; // Error
    bar.b; // Error

    if ('a' in bar) {
        bar.a; // Ok
    } else {
        bar.b; // Ok
    }
}

结语

本篇通过类型检查机制探索了类型检查的规则,下篇通过将探索 TypeScript 的高级类型。

协议

本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。

《探索 TypeScript 类型注解》

上一篇下一篇

猜你喜欢

热点阅读