Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
6 tasks done
zhilidali opened this issue Dec 1, 2019 · 0 comments
Open
6 tasks done

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

zhilidali opened this issue Dec 1, 2019 · 0 comments
Assignees
Labels
ExploringTS Exploring TypeScript TypeScript JavaScript that scales

Comments

@zhilidali
Copy link
Member

zhilidali commented Dec 1, 2019

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 类型推断:当没有显式指定类型注解时,编译器会推断出一个类型。

  • Basic Type Inference 基本类型推断
  • Best Common Type 最佳通用类型
  • Contextual Typing 上下文类型

基本类型推断

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

// 在变量初始化时推断
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 类型断言可以让你告诉编译器当前数据的类型,有两种语法:

  • 尖括号语法: <T>value
  • as 语法: value as T (jsx 中只能用 as 语法)
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

函数参数个数兼容

多兼容少

  • 参数多的兼容参数少的 (主要是因为 JS 中经常可以忽略多余的参数,比如 Array#map),
  • 当 strictFunctionTypes 为 true 时,参数少的不能兼容参数多的

必选参数

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

类型保护

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

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

  • 相等运算符: === !==
  • 逻辑运算符: ||
  • 类型断言运算符: ! 去除 null 和 undefined
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 进一步识别类型

  • typeof: type v === typename 其中 typename 为 boolean, number, string, symbol 时,TS 才会识别为类型保护。
  • instanceof
  • in
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 的高级类型。

协议

CC BY-NC-ND 4.0

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

《探索 TypeScript 类型注解》

  1. 扩展的 JavaScript
  2. 数据类型
  3. 自定义类型
  4. 类型检查
  5. 高级类型
  6. 类型编程

参考链接

Handbook: https://github.com/microsoft/TypeScript-Handbook

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ExploringTS Exploring TypeScript TypeScript JavaScript that scales
Projects
None yet
Development

No branches or pull requests

1 participant