1. Array 和 Tuple
定义数组:
1 2
| let arrOfNumbers: number[] = [1, 2, 3, 4] arrOfNumbers.push("1")
|
Tuple(元组)类似于数组,但是不同于普通 js 数组,元组可以定义每个位置的数据类型。
创建元组:
1 2 3 4
| let user: [string, number] = ['viking', 12]
let user: [string, number] = ['viking'] let user: [string, number] = [12, 'viking']
|
2. Interface 接口
- 对对象的 shape 进行描述
- 对类进行抽象
- Duck Typing
接口的定义:
1 2 3 4 5 6
| interface Person { readonly id: number; name: string; sex: string; age?: number; }
|
接口的使用:
1 2 3 4 5 6 7
| let xiaoming: Person = { id: 1, name: "EsunR", sex: "man", }; xiaoming.age = 19; xiaoming.id = 2
|
接口不仅可以用来定义对象,还可以用来定义函数:
1 2 3 4 5 6 7 8 9
| interface IAdd { (a: number, b: number): number; }
function add(a: number, b: number): number { return a + b; }
const a: IAdd = add;
|
3. Function 函数
创建函数:
1 2 3 4 5 6 7 8 9 10
| function add(x: number, y: number, z?: number): number { if (typeof z === "number") { return x + y + z; } else { return x + y; } }
add(2, 3); add(2, 3, 4);
|
我们还可以对变量定义函数类型,如定义变量 add2
为一个传入值为 3 个 number 类型的变量,且返回为 number 类型的函数,那么 add
函数就可以赋值给这个变量:
1 2 3 4 5 6 7 8 9 10 11
| let add2: (x: number, y: number, z?: number) => number; add2 = add;
let add2: (a: number, b: number, c: number) => number; add2 = add;
let add2: (a: number, b: number) => number; add2 = add;
let add2: (a: number, b: string) => number; add2 = add;
|
3. Class 类
类的基本使用与 ES6 相似,在此不再复述,主要区别在于 TS 中支持了类的修饰符:
public 属性可以让外部实例直接获取到,默认的属性都为 public;而 private 属性只有在类的内部的方法中可以调用,而外部不可调用,也不可继承:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class Animal { public name: string; private age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } walk() { console.log("walk"); } }
let huahua = new Animal("花花", 2); console.log(huahua.name); console.log(huahua.age);
class Dog extends Animal { constructor(name: string, age: number) { super(name, age); this.name = name; this.age = age; } }
|
如果要想让该属性也可被子类继承,那么可以使用 protected 来对变量进行修饰:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class A`nimal { public name: string; - private age: number; + protected age: number; constructor(name: string, age: number) { ... ... } ... ... }
class Dog extends Animal { constructor(name: string, age: number) { super(name, age); this.name = name; this.age = age; } }`
|
静态方法与属性:
1 2 3 4 5 6 7 8 9
| class Animal { static includes = ["dog", "cat", "bird"]; constructor() { } static isAnimal(a) { return a instanceof Animal; } }
|
4. interface 接口
定义与实现接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| interface Radio { switchRadio(): void; }
interface Battery { checkBVatteryStatus(): void; }
interface RadioWithBattery extends Radio { checkBVatteryStatus(): void; }
class Car implements Radio { switchRadio() {} }
class Phone implements Radio, Battery { switchRadio() {} checkBVatteryStatus() {} }
class CellPhone implements RadioWithBattery { switchRadio() {} checkBVatteryStatus() {} }
|
5. enum 枚举
定义与使用枚举类:
1 2 3 4 5 6 7 8
| enum Direction { Up, Down, Left, Right, } console.log(Direction.Up); console.log(Direction[0]);
|
之所以枚举类可以被双向引用,是因为上面的代码被编译为:
1 2 3 4 5 6 7
| var Direction; (function (Direction) { Direction[Direction["Up"] = 0] = "Up"; Direction[Direction["Down"] = 1] = "Down"; Direction[Direction["Left"] = 2] = "Left"; Direction[Direction["Right"] = 3] = "Right"; })(Direction || (Direction = {}));
|
默认结构为:
1 2 3 4 5 6 7 8 9 10
| { '0': 'Up', '1': 'Down', '2': 'Left', '3': 'Right', Up: 0, Down: 1, Left: 2, Right: 3 }
|
除此之外,枚举类还可以设置默认值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| enum Direction { Up = "UP", Down = "DOWN", Left = "LEFT", Right = "RIGHT", } console.log(Direction.Up); console.log(Direction["Up"]); console.log(Direction[0]);
let result = "UP"; if (result !== Direction.Up) { console.log("result error"); }
|
6. 泛型
泛型可以看作是一个占位符,在使用的时候动态填入确定的类型值。
6.1 泛型的简单示例
如果我们定义一个方法,这个方法传入任意类型且返回同样的类型,这样我们可能会将方法定义为:
1 2 3 4 5 6
| function echo(arg: any): any { return arg; } let str = echo("wulalala"); let num: number = echo("123"); console.log(typeof num);
|
但是这样的话就缺少了类型校验,str 会被标为 any 类型,甚至还会出现 BUG。
为了避免这一情况,我们可以定义一个类型相同,但不对类型进行约束的变量类型 T ,我们将 T 称之为泛型:
1 2 3 4 5
| function echo<T>(arg: T): T { return arg } let str = echo("wulalala"); let num: number = echo("123");
|
此外,泛型还可以用于元组中:
1 2 3 4 5
| function swap<T, U>(tuple: [T, U]): [U, T] { return [tuple[1], tuple[0]]; } const result = swap(["string", 123]); console.log(result);
|
6.2 泛型约束
假设现在我们有一个需求:传入一个具有 length 属性的对象,要求输出该对象的 length,同时返回与该对象同一类型的对象。
但是如果我们使用泛型的话,输出 length 时就会显示没有该属性:
1 2 3 4
| function printLength<T>(input: T): T { console.log(input.length); return input; }
|
因此,我们可以使用泛型约束,来约束泛型 T 为一个数组类型:
1 2 3 4 5
| - function printLength<T>(input: T): T { + function printLength<T>(input: T[]): T[] { console.log(input.length); return input; }
|
但是这样的话就失去了泛型原有的作用,用户只能在该方法中传入数组。string 类型同样有 length 属性,但是将 string 传入该方法中的话就会报错。因此更好的做法是去定义一个接口类型,接口类型中拥有 length 属性,string 和 array 都符合接口的规范,我们可以让定义的泛型继承自该接口,那么我们的需求就达到了:
1 2 3 4 5 6 7 8 9 10 11 12 13
| interface IinputWithLength { length: number; }
function printLength<T extends IinputWithLength>(input: T): T { console.log(input.length); return input; }
printLength([1, 2, 3]); printLength("123"); printLength({ length: 10 }); printLength(123);
|
6.3 类和接口的泛型
在前面我们再方法中使用了泛型,那么同样的我们可以在类中也使用泛型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class Queue<T> { private data = []; push(item: T) { return this.data.push(item); } pop(): T { return this.data.pop(); } }
let queue = new Queue<number>(); queue.push(1.23); console.log(queue.pop().toFixed(1));
queue.push("1.23"); console.log(queue.pop().toFixed(1));
|
同样的接口也可以使用泛型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| interface KeyPair<T, U> { key: T; value: U; } let kp1: KeyPair<number, string> = { key: 1, value: "123", };
interface IAdd<T> { (a: T, b: T): T; } function add(a: number, b: number): number { return a + b; } const a: IAdd<number> = add;
|
数组也可以使用泛型来定义:
1 2
| let arr1: number[] = [1, 2, 3]; let arr2: Array<number | string> = [1, 2, 3, "2"];
|
7. 类型别名与断言
7.1 类型别名 Type Aliases
类型别名就是将联合类型或者是比较复杂的函数类型设置一个别名,可以提供给其他变量进行使用,对类型进行约束:
1 2 3 4 5
| type PluseType = (x: number, y: number) => number; function sum(a: number, b: number): number { return a + b; } const fn: PluseType = sum;
|
联合类型比较常用类型别名:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| type NameResolver = () => string; type NameOrResolver = string | NameResolver; function getName(arg: NameOrResolver): string { if (typeof arg === "string") { return arg; } else { return arg(); } }
console.log(getName("huahua")); console.log( getName(function () { return "huahua2"; }) );
|
7.2 类型断言 Type Assertion
可以使用 as
关键字对变量类型进行断言,我们可以将断言后的结果赋值到任意变量上,那么通过这个变量就可以使用我们断言的类型上所拥有的方法:
1 2 3 4 5 6 7 8 9
| function getLength(input: string | number): number { const str = input as string; if (str.length) { return str.length; } else { const number = input as number; return number.toString().length; } }
|
此外,我们可以使用 <>
来更简洁的对变量进行断言并直接使用:
1 2 3 4 5
| if ((<string>input).length) { return (<string>input).length; } else { return (<number>input).toString().length; }
|
两种方式都可以使用 ()
包裹住后直接调用类型上的方法,或者将其赋值到一个变量上,通过变量调用类型上的方法。
# 8. 声明文件
假如我们要在项目中使用 jQuery 文件,那么可能会出现如下报错信息:
此时我们需要 jQuery 的声明文件来帮助我们声明这个方法。我们可以使用关键字 declear
来声明一种方法,让项目可以借助 ts 的能力来对原来使用 js 构建的库文件使用类型断言:
1 2
| declare var jQuery: (selector: string) => any; jQuery("#id");
|
通常我们可以使用 d.ts
文件作为专用的声明文件,这一文件将会被 ts 构建的时候被编译。