类型别名
ts中使用类型别名可以给一个类型起个新名字
期望一个函数接收两种类型,一种函数类型一种字符串类型,不同的类型进行不同的处理,如下:
type Name = string;type NameResolver = () => string;type NameOrResolver = Name | NameResolver;function getName(n: NameOrResolver): Name {if (typeof n === 'string') {return n;} else {return n();}}
字符串字面量类型
字符串字面量类型用来约束取值只能是某几个字符串的其中之一
有点类似于 mst中的types.enum(),如下:
type EventNames = 'click' | 'scroll' | 'mousemove'function handleEvnet(ele: Element, event: EventNames) {// do something}handleEvent(buttonRef.current, 'scroll') // goodhandleEvent(tableRef.current, 'dbClick') // bad,没有这个选项
注意,这里的type不是类型别名,而是定义字符串字面量类型
元组
数组合并了相同类型的对象,而元组合并了不同类型的对象
看到元组,我第一个想起的是:
let numArray: any[] = [1, '2', {a: 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
<a name="DDvdk"></a>### 枚举> 枚举类型用于取值被限定在一定范围内的场景,比如一周只有7天,颜色限定为红绿蓝等- 枚举的成员会被赋值成从 0 开始递增的数字,同时也会对枚举值到枚举名进行反向映射```typescriptenum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat}console.log(Days["Sun"] === 0); // trueconsole.log(Days["Mon"] === 1); // trueconsole.log(Days["Tue"] === 2); // trueconsole.log(Days["Sat"] === 6); // trueconsole.log(Days[0] === "Sun"); // trueconsole.log(Days[1] === "Mon"); // trueconsole.log(Days[2] === "Tue"); // trueconsole.log(Days[6] === "Sat"); // true
手动赋值
我们也可以给枚举类型手动赋值,需要注意的是,未手动赋值的枚举项会接着上一个的枚举项递增,并且如果赋值重复了,ts是不会发现的
enum Days {Sun = 3, Mon = 1, Tue, Wed, Thu, Fri, Sat};console.log(Days["Sun"] === 3); // trueconsole.log(Days["Wed"] === 3); // trueconsole.log(Days[3] === "Sun"); // falseconsole.log(Days[3] === "Wed"); // true
从上面的例子可以看出,赋值为3的sun会被后赋值的 wed所覆盖
常数枚举
常数枚举就是使用const enum 定义的枚举
const enum Directions {up,down,left,right}//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 生成实例,会自动调用构造函数
class Animal {public nameconstructor(name) {this.name = anme}sayHi() {reutrn `hello, ${this.name}`}}let c = new Animal('Jack')c.sayHi() // hello,jack
类的继承
使用 extends 实现继承,子类中使用 super 关键字来电用父类的构造函数和方法
class Cat extends Animal {constructor(name) {super(name) // 调用父类的constructor(anme)console.log(this.name)}sayHi() {return 'Jack,' + super.sayHi() // 调用父类的 sayHi()}}let c = new Cat('Tom') // Tomc.sayHi() // Jack, hello, tom
存取器
使用 getter 和 setter 可以改变属性的赋值和读取行为:
class Animal {constructor(name) {this.name = name}get name() {return 'jack'}set name(value) {console.log('setter' + value)}}let a = new Aniaml('kitty') // setter kittya.name = 'tom' // setter tomconsole.log(a.name) // jack
静态方法
使用 static 修饰的方法称为静态方法,无法实例化,只能用类来调用
class Animal {static isAnimal(a) {return a instanceof Animal}}let a = new Animal('jack')Animal.isAnimal(a) // truea.isAnimal(a) // isAnimal is not a function
ES7中的类
es7中有一些类的提案,ts实现了他们
实例属性
es6中属性只能通过构造函数中的 this.xxx 来定义,es7中可以直接在类里面定义
class Animal {name = 'jack'constructor() {// ...}}let a = new Animal()console.log(a.name) // jack
es7中可以定义一个静态属性
class Animal {static num = 42;constructor() {// ...}}console.log(Animal.num); // 42
TS中的类
public private protected
ts中可以使用这三个修饰符
- public 修饰的方法是公有的,可以在任何地方被访问,默认的属性和方法都是public
- private 修饰的方法和属性是私有的,只能在声明他的类中使用
- protected 修饰的方法和属性是受保护的,但是可以在他的子类中使用
如果一个类的构造函数是 private,那么该类不允许被继承和实例化
如歌一个类的构造函数是 protected ,那么该类只允许被继承
class Animal {public name = 'tom'private age = 12protected sex = '男'constructor(name, age, sex) {this.name = namethis.age = agethis.sex = sex}}class Dog extends Animal {constructor(sex) {super(sex)console.log(this.sex) // 男}}let a = new Animal()a.name // tom
抽象类
abstract 用于定义抽象类和其中的方法
什么是抽象类,抽象类不允许被实例化
abstract class Animal {public nameconstructor(name) {this.name = name}}let a = new Animal('jack') // error
抽象类中的抽象方法必须被子类实现
abstract class Animal {public name;public constructor(name) {this.name = name;}public abstract sayHi();}class Cat extends Animal {public eat() {console.log(`${this.name} is eating.`);}}let cat = new Cat('Tom') // error
报错了,因为子类中没有实现抽象方法 sayHi(), 正确的代码如下:
abstract class Animal {public name;public constructor(name) {this.name = name;}public abstract sayHi();}class Cat extends Animal {public sayHi() {console.log(`Meow, My name is ${this.name}`);}}let cat = new Cat('Tom')
类的类型
ts中表示类如接口类似:
class Animal {name: string;constructor(name: string) {this.name = name}sayHi(): string {return `my name is ${this.name}`}}let a: Animal = new Animal('jack')
类与接口
之前学习过,接口可以对对象的形状进行描述,那么他还有一个用途,对类的一部分行为进行抽象
类实现接口
实现 implements 是面向对象中的一个重要概念,一般来说,一个类只能继承另一个类,有时候不同的类之间可以有一些共同的忒性,这个时候可以吧这些特性抽取成接口,用 implements 关键字实现
举个例子,门是一个类,防盗门是门的一个子类,如果防盗门有一个报警功能,我们可以给防盗门添加一个报警的方法,这个时候有另外一个类,车,他也有报警的功能,那么我们可以考虑把报警功能提取出来,实现一个接口,防盗门和车都去实现他
interface Alarm {alert(): void;}class Door {}class SecurityDoor extends Door implements Alarm {alert() {console.log('call 110')}}class Car implements Alarm {alert() {console.log('call 110')}}
一个类也可以实现多个接口:
interface Alarm {alert() :void}interface Light {lightOn(): void;lightOff(): void;}class Car implements Alarm, Light {alert() {console.log('car alert')}lightOn {console.log('cat light on')}lightOff {cosnole.log('car light off')}}
car实现了 Alarm 和 Light 接口
接口继承接口
接口之间也是可以继承的
interface Alarm {alert(): void}interface Light extends Alarm {lightOn(): voidlightOff(): void}
Light 继承了 Alarm之后,他除了自己的方法还将拥有 alert 方法
接口继承类
常见的面向对象语言中,接口无法继承类,但是ts中可以, 因为在声明类的时候,除了创建了一个类之外,还创建了一个类型,那么接口是可以继承类型的
class Point {x: numbery: numberconstructor(x: number, y: number) {this.x =xthis.y = y}}interface Point3d extends Point {z:number}let point3d: Point3d = {x:1, y:2, z:3}
泛型
泛型是指在定义函数,接口,或者类的时候,不预先指定具体的类型,而在使用的时候再指定的一种特性
首先实现一个函数,他可以创建一个指定长度的数组,同时将每一项都填充一个默认值
function createArray(length:number, value:any):Array<any> {let result = []for (let i = 0; i< length; i++) {result[i] = value}return result}createArray(3, 'x') // ['x', 'x', 'x']
这段代码有一个缺陷,那就是没有准确的定义返回值的类型,在预期中,数组每一项都应该是输入的value的类型
那么可以用泛型来解决这个问题
function createArray<T>(length: number, value: T): Array<T> {let result:T[] = []for(let i = 0; i < length; i++) {result[i] = value}return reslult}createArray<string>(3, 'x') // ['x', 'x', 'x']
上面的例子中,我们在函数名后面添加了
接着在调用的时候,可以指定他的具体类型为 string, 如果没有手动指定,也可以让类型推论自动算出来
多个类型的参数
定义泛型的时候,可以一次定义多个类型
function swap<T, U>(tuple: [T, U]): [U, T] {return [tuple[1], typle[0]]}swap([6, 'six']) // ['six', 6]
泛型约束
当我们在使用泛型的时候,因为不知道他是哪一种类型,所以不能随意操作他的属性或者方法
function login<T>(arg: T): T {consol.log(arg.length)return arg}// error
上述例子中,因为泛型T不一定包含属性 length
那么我们可以通过定义泛型约束,只允许这个函数传入那些带有 length 属性的变量,这样就可以操作
interface Lengthwise {length: number;}function login<T extends Lengthwise>(arg: T): T {console.log(arg.length)return arg}// 传入的参数必须带有length属性login({length: 7})
多个泛型之间可以互相约束,
function copyFields<T extends U, U>(target: T, source: U): T {for (let id in source) {target[id] = (<T>source)[id];}return target;}let x = { a: 1, b: 2, c: 3, d: 4 };copyFields(x, { b: 10, d: 20 });
上面的例子中,要求 T 继承 U, 这样就保证了 U 上不会出现 T 中不存在的字段
