TypeScript 是 JavaScript 的一个超集,主要提供了类型系统对 ES6 的支持,它由 Microsoft 开发,代码开源于 GitHub 上。

基础类型

JS 中有 5 中基本类型:boolean,number,string,null,undefined,两种引用类型:Array,Object。Typescript(以下简称 TS)支持与 JS 相同的数据类型,此外还提供枚举等类型。

boolean

在 TS 中,类型标注通过:TypeAnnotation语法,比如 boolean 类型:

1
let isDone: boolean = false

number

TS 同 JS 一样,所有的数字都是浮点数。除了十进制喝十六进制,TS 还支持 ES6 中引入的二进制喝八进制字面量:

1
2
3
4
let decLiteral: number = 6
let hexLiteral: number = 0xf00d
let binaryLiteral: number = 0b1010
let octalLiteral: number = 0o744

string

1
2
3
4
5
let name: string = 'Gene'
let age: number = 37
let sentence: string = `Hello, my name is ${name}.

I'll be ${age + 1} years old next month.`

void

void表示没有任何类型,可以用来表示没有任何返回值的函数:

1
2
3
function warnUser(): void {
console.log('This is my warning message')
}

声明一个void类型的便利没什么用,只能将它赋值为undefinednull

1
let unusable: void = undefined

null 和 undefined

默认情况下nullundefined是所有类型的子类型。 就是说你可以把 nullundefined赋值给number类型的变量。然而,当你指定了--strictNullChecks标记,nullundefined只能赋值给 void 和它们各自。

any

any表示允许被赋值为任意类型:

1
2
3
let notSure: any = 4
notSure = 'maybe a string instead'
notSure = false // okay, definitely a boolean

访问任意类似元素的任何属性和方法,都不会引起 TS 报错:

1
2
3
4
5
6
let anyThing: any = 'hello'
console.log(anyThing.myName)
console.log(anyThing.myName.firstName)
anyThing.setName('Jerry')
anyThing.setName('Jerry').sayHello()
anyThing.myName.setFirstName('Cat')

注意:变量在声明时,如果为指定类型,则默认被识别为any类型。

never

never类型表示永远不存在的值的类型。比如:never 类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型; 变量也可能是 never 类型,当它们被永不为真的类型保护所约束时。

never 类型是任何类型的子类型,也可以赋值给任何类型;然而,没有类型是 never 的子类型或可以赋值给 never 类型(除了 never 本身之外)。 即使 any 也不可以赋值给 never。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
throw new Error(message)
}

// 推断的返回值类型为never
function fail() {
return error('Something failed')
}

// 返回never的函数必须存在无法达到的终点
function infiniteLoop(): never {
while (true) {}
}

数组

TS 有两种方式表示数组元素,一种是:TypeAnnotation[]

1
let list: number[] = [1, 2]

另一种是:Array:

1
let list: Array<number> = [1, 2, 3]

元组

元组表示已知元素数量和类型的数组,各元素的类型不一定是相同的,比如:

1
2
3
4
5
let x: [string, number]

x = ['hello', 10] // OK

x = [10, 'hello'] // Error

注意,当访问一个越界元素时,TS 将使用联合类型进行约束:

1
2
3
4
5
x[3] = 'world' // OK, 字符串可以赋值给(string | number)类型

console.log(x[5].toString()) // OK, 'string' 和 'number' 都有 toString

x[6] = true // Error, 布尔不是(string | number)类型

object

1
2
3
4
5
6
7
8
9
declare function create(o: object | null): void

create({ prop: 0 }) // OK
create(null) // OK

create(42) // Error
create('string') // Error
create(false) // Error
create(undefined) // Error

对象的类型

在 TS 中,使用接口来定义对象的类型。接口是对行为的抽象,除了可用于对类的一部分行为进行抽象外,也常用于对对象形状进行描述。

1
2
3
4
5
6
7
8
interface Person {
name: string
age: number
}
let tom: Person = {
name: 'Tom',
age: 25
}

在上例中,变量tom的类似是Person,这就意味着tom的形状必须和接口Person一致,不能多也不能少,如下两个定义都是不允许的:

1
2
3
4
5
6
7
8
9
let tom: Person = {
name: 'Tom'
}

let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
}

可选属性

可选属性在定义接口的属性名后加?,即使有可选属性,仍然不允许添加未定义的属性:

1
2
3
4
interface Person {
name: string
age?: number
}

任意属性

1
2
3
4
5
interface Person {
name: string
age?: number
[propName: string]: any
}

[propName: string]定义了任意属性的 key 的类型应是string,value 值的类型必须是其他确定属性、可选属性的超类。

1
2
3
4
5
6
7
8
9
10
11
interface Person {
name: string
age?: number
[propName: string]: string
}

let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
}

上例中,任意属性的值允许是 string,但是可选属性 age 的值却是 numbernumber 不是 string 的子属性,所以报错了。

只读属性

只读属性通过在接口对象属性名前加readonly定义,在声明属性时就开始进行约束了,而不是第一次赋值时:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface Person {
readonly id: number
name: string
age?: number
[propName: string]: any
}

let tom: Person = {
name: 'Tom',
gender: 'male'
}

tom.id = 89757

// index.ts(8,5): error TS2322: Type '{ name: string; gender: string; }' is not assignable to type 'Person'.
// Property 'id' is missing in type '{ name: string; gender: string; }'.
// index.ts(13,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.

函数的类型

一个函数有输入和输出,要再 TS 中对其进行约束,需要把输入和输出都考虑到。

函数声明

1
2
3
function sum(x: number, y: number): number {
return x + y
}

函数表达式

1
2
3
let sum = function(x: number, y: number): number {
return x + y
}

上面的代码只是对等号右侧的匿名函数进行了定义,而等号左侧的sum,是通过赋值操作进行类型推论推断出来的,如果我们手动给sum添加类型,应该是这样的:

1
2
3
4
5
6
let sum: (x: number, y: number) => number = function(
x: number,
y: number
): number {
return x + y
}

在 TS 的类型定义中,=>用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。

在 ES6 中,=>是箭头函数。

联合类型

联合类型表示取值可以为多种类型中的一种,使用|分隔每个类型:

1
2
3
4
5
6
7
let myFavoriteNumber: string | number
myFavoriteNumber = 'seven'
myFavoriteNumber = 7

myFavoriteNumber = true
// index.ts(2,1): error TS2322: Type 'boolean' is not assignable to type 'string | number'.
// Type 'boolean' is not assignable to type 'number'.

当 TS 不确定一个联合类型的变量到底是哪个类型的时候,你只能访问共有的属性或方法:

1
2
3
4
5
6
function getLength(something: string | number): number {
return something.length
}

// index.ts(2,22): error TS2339: Property 'length' does not exist on type 'string | number'.
// Property 'length' does not exist on type 'number'.

类型断言

类型断言(Type Assertion)指手动指定一个值的类型。语法为<类型>值值 as 类型

上一节中讲到,联合类型的变量在不确定是哪个类型时,只能访问共有的属性或方法。但有时,确实需要在不确定类型的情况下,访问其中一种类型的属性或方法,比如:

1
2
3
4
5
6
7
8
9
10
11
12
function getLength(something: string | number): number {
if (something.length) {
return something.length
} else {
return something.toString().length
}
}

// index.ts(2,19): error TS2339: Property 'length' does not exist on type 'string | number'.
// Property 'length' does not exist on type 'number'.
// index.ts(3,26): error TS2339: Property 'length' does not exist on type 'string | number'.
// Property 'length' does not exist on type 'number'.

你可以使用类型断言来解决上述问题:

1
2
3
4
5
6
function getLength(something: string | number): number {
if ((something as string).length) {
return (something as string).length
}
return something.toString().length
}

注意:你不能断言一个不存在与联合类型中的类型:

1
2
3
4
5
6
function toBoolean(something: string | number): boolean {
return <boolean>something
}

// index.ts(2,10): error TS2352: Type 'string | number' cannot be converted to type 'boolean'.
// Type 'number' is not comparable to type 'boolean'.

类型推论

如果没有明确的指定类型,TS 会依照类型推论(Type Inference)的规则推断出一个类型。

1
2
3
4
let foo = 123 // foo 是 'number'
let bar = 'hello' // bar 是 'string'

foo = bar // Error: 不能将 'string' 赋值给 `number`

当需要从几个表达式中推断类型时,会根据所以表达式的类型推断出一个最合适的通用类型,比如:

1
let x = [0, 1, null]

为了推断x的类型,必须考虑所有元素的类型,所以类型推断的结果是联合数字类型(number|null)[]

类型别名

类型别名使用type给一个类型创建新名字,例如:

1
2
3
4
5
6
7
8
9
10
type Name = string
type NameResolver = () => string
type NameOrResolver = Name | NameOrResolver
function getName(n: NameOrResolver): Name {
if (typeof n === 'string') {
return n
} else {
return n()
}
}

枚举

enum类型是对 JS 标准数据类型的一个补充。你可以使用枚举定义一些带名字的常量。TS 支持数字和基于字符串的枚举。

数字枚举

1
2
3
4
5
6
7
8
9
10
enum Response {
No,
Yes
}

function respond(recipient: string, message: Response): void {
// ...
}

respond('Princess Caroline', Response.Yes)

默认情况,从0开始为元素编号,后面元素自动加 1。你可以手动指定成员的数值:

1
2
3
4
5
6
enum Direction {
Up = 1,
Down,
Left,
Right
}

如上,我们定义了一个数字枚举, Up 使用初始化为 1。 Direction.Up 的值为 1, Down 为 2, Left 为 3, Right 为 4。

字符串枚举

字符串枚举里每个成员都必须用字符串,或另外一个字符串枚举成员进行初始化。

1
2
3
4
5
6
enum Direction {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT'
}

运行时的枚举

枚举在运行时以对象的形式存在:

1
2
3
4
5
6
7
8
9
enum E {
X,
Y,
Z
}
function f(obj: { X: number }) {
return obj.X
}
f(E)

上面的代码并不会报错,因为枚举E在运行时是一个对象,有X属性且值为 0。

枚举成员会被赋值为从 0 开始递增的数字,同时也会对枚举值到枚举名进行反向映射:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
enum Days {
Sun,
Mon,
Tue,
Wed,
Thu,
Fri,
Sat
}

console.log(Days['Sun'] === 0) // true
console.log(Days['Mon'] === 1) // true
console.log(Days['Tue'] === 2) // true
console.log(Days['Sat'] === 6) // true

console.log(Days[0] === 'Sun') // true
console.log(Days[1] === 'Mon') // true
console.log(Days[2] === 'Tue') // true
console.log(Days[6] === 'Sat') // true

TS 会将上面这段代码编译为下面的 JS:

1
2
3
4
5
6
7
8
9
10
var Days
;(function(Days) {
Days[(Days['Sun'] = 0)] = 'Sun'
Days[(Days['Mon'] = 1)] = 'Mon'
Days[(Days['Tue'] = 2)] = 'Tue'
Days[(Days['Wed'] = 3)] = 'Wed'
Days[(Days['Thu'] = 4)] = 'Thu'
Days[(Days['Fri'] = 5)] = 'Fri'
Days[(Days['Sat'] = 6)] = 'Sat'
})(Days || (Days = {}))

注意:字符串枚举不会生成反向映射。

常量枚举

常量枚举是使用const enum定义的的枚举类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
const enum Directions {
Up,
Down,
Left,
Right
}

let directions = [
Directions.Up,
Directions.Down,
Directions.Left,
Directions.Right
]

常量枚举与普通枚举的区别是,它会再编译节点被删除,并且不能包含计算成员。上例的编译结果是:

1
var directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */]

泛型

泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。

设计泛型的关键目的是在成员之间提供有意义的约束,这些成员可以是:

  • 类的实例成员
  • 类的方法
  • 函数参数
  • 函数返回值

下面来创建一个使用泛型的例子:identity 函数。这个函数会返回任何传入它的值:

1
2
3
function identity<T>(arg: T): T {
return arg
}

identity函数名后添加了——类型变量TT可以捕获用户传入的类型,之后就可以使用这个类T,当做返回值类型。

泛型函数可以使用两种方法调用,第一种是传入所有的参数,包括类型参数:

1
let output = identity<string>('myString')

第二种是利用类型推断确定T的类型:

1
let output = identity('myString')

参考文献

  1. TypeScript 官方文档
  2. TypeScript 入门教程 by xcatliu
  3. 深入理解 TypeScript by 三毛