类型别名

ts中使用类型别名可以给一个类型起个新名字

期望一个函数接收两种类型,一种函数类型一种字符串类型,不同的类型进行不同的处理,如下:

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

字符串字面量类型

字符串字面量类型用来约束取值只能是某几个字符串的其中之一

有点类似于 mst中的types.enum(),如下:

  1. type EventNames = 'click' | 'scroll' | 'mousemove'
  2. function handleEvnet(ele: Element, event: EventNames) {
  3. // do something
  4. }
  5. handleEvent(buttonRef.current, 'scroll') // good
  6. handleEvent(tableRef.current, 'dbClick') // bad,没有这个选项

注意,这里的type不是类型别名,而是定义字符串字面量类型

元组

数组合并了相同类型的对象,而元组合并了不同类型的对象

看到元组,我第一个想起的是:

  1. let numArray: any[] = [1, '2', {a: 1}] // 子项可以为任意类型

那么元组的区别是,子项只允许是他定义的类型

  1. let tom: [number, string] = [25, 'toim']

需要注意的是以下几点:

  • 可以只赋值其中一项,但是如果对整个元组赋值,就必须提供所有类型的指定项
  • 可以添加越界的元素,但是越界元素的类型会被限制为规定类型的其中之一 ```typescript let tom: [number, string] tom[1] = ‘tom’ // good tom = [2] // bad, error

tom = [1, ‘tom’] tom.push(2) // good tom.push(false) // bad, error

  1. <a name="DDvdk"></a>
  2. ### 枚举
  3. > 枚举类型用于取值被限定在一定范围内的场景,比如一周只有7天,颜色限定为红绿蓝等
  4. - 枚举的成员会被赋值成从 0 开始递增的数字,同时也会对枚举值到枚举名进行反向映射
  5. ```typescript
  6. enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat}
  7. console.log(Days["Sun"] === 0); // true
  8. console.log(Days["Mon"] === 1); // true
  9. console.log(Days["Tue"] === 2); // true
  10. console.log(Days["Sat"] === 6); // true
  11. console.log(Days[0] === "Sun"); // true
  12. console.log(Days[1] === "Mon"); // true
  13. console.log(Days[2] === "Tue"); // true
  14. console.log(Days[6] === "Sat"); // true

手动赋值
我们也可以给枚举类型手动赋值,需要注意的是,未手动赋值的枚举项会接着上一个的枚举项递增,并且如果赋值重复了,ts是不会发现的

  1. enum Days {Sun = 3, Mon = 1, Tue, Wed, Thu, Fri, Sat};
  2. console.log(Days["Sun"] === 3); // true
  3. console.log(Days["Wed"] === 3); // true
  4. console.log(Days[3] === "Sun"); // false
  5. console.log(Days[3] === "Wed"); // true

从上面的例子可以看出,赋值为3的sun会被后赋值的 wed所覆盖

常数枚举
常数枚举就是使用const enum 定义的枚举

  1. const enum Directions {
  2. up,
  3. down,
  4. left,
  5. right
  6. }
  7. //
  8. let directions = [Directions.up, Directions.down, Directions.left, Directions.right]

传统方法中,js通过构造函数实现类的概念,而在es6中,有了class

类的概念
虽然js有类的概念,但是ts更加精准

  • 类:定义了一件事物的抽象特点,包含属性以及方法
  • 对象:类的实例,通过new生成
  • 面向对象三大特性: 封装,继承,多态
  • 封装:将对数据操作的细节隐藏,只暴露对外的接口,外界调用不需要知道细节,只需要调用即可
  • 继承:子类继承父类,除了拥有父类的所有的特性之外,还有一些更具体的特性
  • 多态:由于继承而产生的不同的类,对一个方法可以有不同的影响,如 cat 和 dog 都继承与 animal,他们都有自己的eat方法,此时针对一个实例,我们不需要了解是cat还是dog,直接可以调用eat方法,程序会自动判断
  • 存取器:用于改变属性的读取和赋值行为
  • 修饰符:修饰符是一些关键字,用于限定成员或者类型的性质,如 public 表示公有方法和属性
  • 抽象类: 抽象类是提供其他类继承的基类,抽象类不允许被实例化,抽象类中的的抽象方法必须在子类中被实现
  • 接口: 不同类之间的公有的属性或方法,可以抽象成一个接口,接口可以被类实现

为了加深印象,我们从es6中的类开始看起

ES6中的类

使用 class 定义类,使用 constructor 定义构造函数
通过 new 生成实例,会自动调用构造函数

  1. class Animal {
  2. public name
  3. constructor(name) {
  4. this.name = anme
  5. }
  6. sayHi() {
  7. reutrn `hello, ${this.name}`
  8. }
  9. }
  10. let c = new Animal('Jack')
  11. c.sayHi() // hello,jack

类的继承
使用 extends 实现继承,子类中使用 super 关键字来电用父类的构造函数和方法

  1. class Cat extends Animal {
  2. constructor(name) {
  3. super(name) // 调用父类的constructor(anme)
  4. console.log(this.name)
  5. }
  6. sayHi() {
  7. return 'Jack,' + super.sayHi() // 调用父类的 sayHi()
  8. }
  9. }
  10. let c = new Cat('Tom') // Tom
  11. c.sayHi() // Jack, hello, tom

存取器
使用 getter 和 setter 可以改变属性的赋值和读取行为:

  1. class Animal {
  2. constructor(name) {
  3. this.name = name
  4. }
  5. get name() {
  6. return 'jack'
  7. }
  8. set name(value) {
  9. console.log('setter' + value)
  10. }
  11. }
  12. let a = new Aniaml('kitty') // setter kitty
  13. a.name = 'tom' // setter tom
  14. console.log(a.name) // jack

静态方法
使用 static 修饰的方法称为静态方法,无法实例化,只能用类来调用

  1. class Animal {
  2. static isAnimal(a) {
  3. return a instanceof Animal
  4. }
  5. }
  6. let a = new Animal('jack')
  7. Animal.isAnimal(a) // true
  8. a.isAnimal(a) // isAnimal is not a function

ES7中的类

es7中有一些类的提案,ts实现了他们

实例属性
es6中属性只能通过构造函数中的 this.xxx 来定义,es7中可以直接在类里面定义

  1. class Animal {
  2. name = 'jack'
  3. constructor() {
  4. // ...
  5. }
  6. }
  7. let a = new Animal()
  8. console.log(a.name) // jack

es7中可以定义一个静态属性

  1. class Animal {
  2. static num = 42;
  3. constructor() {
  4. // ...
  5. }
  6. }
  7. console.log(Animal.num); // 42

TS中的类

public private protected
ts中可以使用这三个修饰符

  • public 修饰的方法是公有的,可以在任何地方被访问,默认的属性和方法都是public
  • private 修饰的方法和属性是私有的,只能在声明他的类中使用
  • protected 修饰的方法和属性是受保护的,但是可以在他的子类中使用

如果一个类的构造函数是 private,那么该类不允许被继承和实例化
如歌一个类的构造函数是 protected ,那么该类只允许被继承

  1. class Animal {
  2. public name = 'tom'
  3. private age = 12
  4. protected sex = '男'
  5. constructor(name, age, sex) {
  6. this.name = name
  7. this.age = age
  8. this.sex = sex
  9. }
  10. }
  11. class Dog extends Animal {
  12. constructor(sex) {
  13. super(sex)
  14. console.log(this.sex) // 男
  15. }
  16. }
  17. let a = new Animal()
  18. a.name // tom

抽象类
abstract 用于定义抽象类和其中的方法
什么是抽象类,抽象类不允许被实例化

  1. abstract class Animal {
  2. public name
  3. constructor(name) {
  4. this.name = name
  5. }
  6. }
  7. let a = new Animal('jack') // error

抽象类中的抽象方法必须被子类实现

  1. abstract class Animal {
  2. public name;
  3. public constructor(name) {
  4. this.name = name;
  5. }
  6. public abstract sayHi();
  7. }
  8. class Cat extends Animal {
  9. public eat() {
  10. console.log(`${this.name} is eating.`);
  11. }
  12. }
  13. let cat = new Cat('Tom') // error

报错了,因为子类中没有实现抽象方法 sayHi(), 正确的代码如下:

  1. abstract class Animal {
  2. public name;
  3. public constructor(name) {
  4. this.name = name;
  5. }
  6. public abstract sayHi();
  7. }
  8. class Cat extends Animal {
  9. public sayHi() {
  10. console.log(`Meow, My name is ${this.name}`);
  11. }
  12. }
  13. let cat = new Cat('Tom')

类的类型
ts中表示类如接口类似:

  1. class Animal {
  2. name: string;
  3. constructor(name: string) {
  4. this.name = name
  5. }
  6. sayHi(): string {
  7. return `my name is ${this.name}`
  8. }
  9. }
  10. let a: Animal = new Animal('jack')

类与接口

之前学习过,接口可以对对象的形状进行描述,那么他还有一个用途,对类的一部分行为进行抽象

类实现接口
实现 implements 是面向对象中的一个重要概念,一般来说,一个类只能继承另一个类,有时候不同的类之间可以有一些共同的忒性,这个时候可以吧这些特性抽取成接口,用 implements 关键字实现

举个例子,门是一个类,防盗门是门的一个子类,如果防盗门有一个报警功能,我们可以给防盗门添加一个报警的方法,这个时候有另外一个类,车,他也有报警的功能,那么我们可以考虑把报警功能提取出来,实现一个接口,防盗门和车都去实现他

  1. interface Alarm {
  2. alert(): void;
  3. }
  4. class Door {}
  5. class SecurityDoor extends Door implements Alarm {
  6. alert() {
  7. console.log('call 110')
  8. }
  9. }
  10. class Car implements Alarm {
  11. alert() {
  12. console.log('call 110')
  13. }
  14. }

一个类也可以实现多个接口:

  1. interface Alarm {
  2. alert() :void
  3. }
  4. interface Light {
  5. lightOn(): void;
  6. lightOff(): void;
  7. }
  8. class Car implements Alarm, Light {
  9. alert() {
  10. console.log('car alert')
  11. }
  12. lightOn {
  13. console.log('cat light on')
  14. }
  15. lightOff {
  16. cosnole.log('car light off')
  17. }
  18. }

car实现了 Alarm 和 Light 接口

接口继承接口
接口之间也是可以继承的

  1. interface Alarm {
  2. alert(): void
  3. }
  4. interface Light extends Alarm {
  5. lightOn(): void
  6. lightOff(): void
  7. }

Light 继承了 Alarm之后,他除了自己的方法还将拥有 alert 方法

接口继承类
常见的面向对象语言中,接口无法继承类,但是ts中可以, 因为在声明类的时候,除了创建了一个类之外,还创建了一个类型,那么接口是可以继承类型的

  1. class Point {
  2. x: number
  3. y: number
  4. constructor(x: number, y: number) {
  5. this.x =x
  6. this.y = y
  7. }
  8. }
  9. interface Point3d extends Point {
  10. z:number
  11. }
  12. let point3d: Point3d = {x:1, y:2, z:3}

泛型

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

首先实现一个函数,他可以创建一个指定长度的数组,同时将每一项都填充一个默认值

  1. function createArray(length:number, value:any):Array<any> {
  2. let result = []
  3. for (let i = 0; i< length; i++) {
  4. result[i] = value
  5. }
  6. return result
  7. }
  8. createArray(3, 'x') // ['x', 'x', 'x']

这段代码有一个缺陷,那就是没有准确的定义返回值的类型,在预期中,数组每一项都应该是输入的value的类型
那么可以用泛型来解决这个问题

  1. function createArray<T>(length: number, value: T): Array<T> {
  2. let result:T[] = []
  3. for(let i = 0; i < length; i++) {
  4. result[i] = value
  5. }
  6. return reslult
  7. }
  8. createArray<string>(3, 'x') // ['x', 'x', 'x']

上面的例子中,我们在函数名后面添加了 , 其中 T 是用来指代任意输入的类型,在后面输入的 vlaue: T和输出的Array就可以使用了
接着在调用的时候,可以指定他的具体类型为 string, 如果没有手动指定,也可以让类型推论自动算出来

多个类型的参数
定义泛型的时候,可以一次定义多个类型

  1. function swap<T, U>(tuple: [T, U]): [U, T] {
  2. return [tuple[1], typle[0]]
  3. }
  4. swap([6, 'six']) // ['six', 6]

泛型约束
当我们在使用泛型的时候,因为不知道他是哪一种类型,所以不能随意操作他的属性或者方法

  1. function login<T>(arg: T): T {
  2. consol.log(arg.length)
  3. return arg
  4. }
  5. // error

上述例子中,因为泛型T不一定包含属性 length
那么我们可以通过定义泛型约束,只允许这个函数传入那些带有 length 属性的变量,这样就可以操作

  1. interface Lengthwise {
  2. length: number;
  3. }
  4. function login<T extends Lengthwise>(arg: T): T {
  5. console.log(arg.length)
  6. return arg
  7. }
  8. // 传入的参数必须带有length属性
  9. login({length: 7})

多个泛型之间可以互相约束,

  1. function copyFields<T extends U, U>(target: T, source: U): T {
  2. for (let id in source) {
  3. target[id] = (<T>source)[id];
  4. }
  5. return target;
  6. }
  7. let x = { a: 1, b: 2, c: 3, d: 4 };
  8. copyFields(x, { b: 10, d: 20 });

上面的例子中,要求 T 继承 U, 这样就保证了 U 上不会出现 T 中不存在的字段