Typescript - 基础(三)

2020-05-03  本文已影响0人  酷热summer

接口、函数、类

1、接口

作用:可以用来约束函数、对象以及类的结构和类型。

1.1 对象类型的接口

interface List {      // 定义接口 list
    id: number;
    name: string;
}
interface Result {      // 定义接口 result,由 list 数组组成
    data: List[]
}
function render(result: Result) {      // 渲染方法
    result.data.forEach((value) => {
        console.log(value.id, value.name)
    })
}
let result = {        // 假定接口返回的数据
    data: [
        {id: 1, name: 'A'},
        {id: 2, name: 'B'}
    ]
}
render(result)

考虑其他情况:

1.1.1 result 返回数据如下:
let result = {        // 假定接口返回的数据
    data: [
        {id: 1, name: 'A', sex: 'male'},
        {id: 2, name: 'B'}
    ]
}

此时,ts 并没有报错,ts 允许这种情况发生的。主要是因为,如果传入的对象,满足接口的必要条件,那么就是被允许的。即便有多余的字段,也可以通过类型检查。

如果 render 方法中直接传入对象字面量的话, ts 就会对额外的参数进行类型检查。绕过这种检查的方法一共有三种:

// 类型断言的含义是,告诉编译器,我们使用的类型是 Result
render({
  data:  [
        {id: 1, name: 'A', sex: 'male'},
        {id: 2, name: 'B'}
    ]
} as Result)
// 上述方法的等价写法,但是不建议使用
render(<Result>{
  data:  [
        {id: 1, name: 'A', sex: 'male'},
        {id: 2, name: 'B'}
    ]
})
interface List {      // 定义接口 list
    id: number;
    name: string;
    [x: string]: any;        // 中括号内定义一个 x,返回值类型是 any,含义是用任意的字符串索引 List,以使 List 支持多个属性
}
1.1.2 不确定数据,使用可选属性

如果 List 中可能返回 age 也可能不返回,则可改写为:

interface List { 
    id: number;
    name: string;
    age?: number;
}
1.1.3 只读属性
interface List { 
    readonly id: number;
    name: string;
    age?: number;
}
// 如果在 function render(){} 中对某一个 id 进行修改,则会报错
1.1.4 可索引类型接口

当接口中的数据个数不确定的时候,可以使用。可以用数字/字符串进行索引。

interface StringArray {
  [index: number] : string;
}
// 用任意的数字去索引 StringArray 都可以得到一个 string,相当于声明了一个字符串数组
let arr: StringArray = ['a', 'b'];
interface Names {
  [x: string] : string;
}
let arr: StringArray = ['a', 'b'];
// 用任意的数字去索引 Names 都可以得到一个 string,相当于声明了一个字符串数组
// 如果在里面再声明一个数字类型的数据,是不被允许的,如:
interface Names1 {
  [x: string] : string;
  y: number;        // 会报错
}
// 如果新增一个数字索引,但数据类型是 string ,是被允许的。

1.2 函数类型的接口

用变量定义函数类型:let add = (x:number, y:number) => number;除此之外,还可以用接口定义函数类型。

interface Add {
  (x:number, y:number) :number;
}
// 类型别名定义,更加简单
type Add = (x:number, y:number) => number;
let add1: Add = (a, b) =>  a+b;

1.3 混合类型的接口

一个接口,既可以定义一个函数,又像对象一样,拥有属性和方法:

interface Lib {
  ():void;      // 函数
  version: string;    // 属性
  fn():void;      // 方法
}
function getlib() {
  let lib:Lib = (() => {}) as Lib;
  lib.version = '1.1.1.1';
  lib.fn = () => {};
  return lib;
}
let lib1 = getlib();

2 函数

2.1 目前函数定义的几种方法:

function add1(x: number, y: number) {
    return x + y
}

let add2: (x: number, y: number) => number

type add3 = (x: number, y: number) => number

interface add4 {
    (x: number, y: number): number
}

后三种只是函数类型的定义,并没有具体的实现。

2.2 函数重载

在 C/C++ 中,两个函数,如果函数名称相同,但是参数个数/类型不同,那么就实现了函数重载。不需要为功能相似的函数,采用不同的函数名称。

ts 的重载,要求先定义出一系列函数名称相同的声明。

function add (...rest:number[]):number;
function add (...rest:string[]): string;
function add (...rest: any): any {
  let type = typeof rest[0];
  if(type === 'string') return rest.join('');
  if(type === 'number') return rest.reduce((pre,cur)=> pre + cur);
}

3 类

3.1 类的继承

// 普通 class
class Dog {
  constructor(name: string) {    // 构造函数的参数添加类型注解
    this.name = name;
  }
  name: string;      // 为成员属性添加类型注解
  run () {};
}
// 类成员的属性都是实例属性,而不是原型属性
// 类成员的方法同理

// class 的继承
class Husky extends Dog {
  constructor (name: string, color: string) {
    super(name);        // 代表父类的实例
    this.color = color;
  }
  color: string;
}

3.2 类的成员修饰符

class Dog {
  constructor(name: string) { 
    this.name = name;
  }
  public name: string;
  public run () {};
}
class Dog {
  constructor(name: string) { 
    this.name = name;
  }
  public name: string;
  public run () {};
  private fn () {}
}
let dog = new Dog();
dog.fn();       // 此处会报错,子类同理

如果在构造函数处添加 private,那么表明这个类 既不能被实例化也不能被继承

// 此处的 class 定义跟直接定义 name 属性的效果一致
class Dog {
  constructor(public name: string) { 
    this.name = name;
  }
}
class Dog {
  constructor(public name: string) { 
    this.name = name;
  }
  static food: string = 'meat';
}
let dog = new Dog();
console.log(Dog.food);    // meat
console.log(dog.food);    // error

静态成员可以被继承,可以通过子类直接调用。

3.2 抽象类和多态

3.2.1 抽象类

ES 中没有引入抽象类的概念,这个是 TS 对 ES 的扩展。抽象类是只能被继承不能被实例化的类。

// 定义抽象类
abstract class Animal {
  eat() {
    console.log('eat');
  }
  abstract sleep ():void;      // 定义一个方法,但是不指定其具体的实现,子类可以具体去实现
}
let cat = new Animal();    // Error
class Dog extend Animal {
  constructor(name: string) { 
    super();
    this.name = name;
  }
  name: string;
  run () {};
  sleep () {
    console.log('dog sleep');
  }
}
let dog = new Dog('wangwang');
dog.eat();         // eat

3.2.2 多态

在父类中定义一个抽象方法,在多个子类中对这个方法有不同的实现,在程序运行时,针对不同的对象,进行不同的操作,以实现运行时绑定。

// 根据 3.2.1 的示例继续写
class Cat extends Animal{
  sleep() {
      console.log('cat sleep');
  }
}
let cat = new Cat();
let anmials : Animal[] = [dog, cat];
anmials.forEach(i => i.sleep());       // 'dog sleep', 'cat sleep'

实现一个简单的链式调用:

class workFlow {
  step1 () {
    return this;
  };
  step2 () {
    return this;
  }
}
new workFlow().step1().step2();
// 实现父类和子类之间的连贯调用
class myFlow extends workFlow {
  next() {
    return this;
   }
}
new myFlow().next().step1().next().step2();

4、类与接口的关系

4.1 类类型接口

// 类类型接口
interface Human {      // 定义一个接口,接口约束类成员有哪些属性以及类型
    name: string;
    eat(): void;
}
class Asian implements Human {
    constructor(name: string) {
        this.name = name;
    }
    name: string
    eat() {}
    sleep () {}        // name 和 eat 是必须的属性和方法, sleep 为 class 自身定义的,是被允许的
}

类类型接(interface Human)口必须具有以下特性:

4.2 接口继承接口

接口可以相互继承,且一个接口可以继承多个接口

interface Human {        // 
    name: string;
    eat(): void;
}
interface Man extends Human {
    run(): void
}
interface Child {
    cry(): void
}
interface Boy extends Man, Child {}        // Boy 接口同时继承 Man 和 Child
let boy: Boy = {      // Boy 需要包含所有继承的属性和方法
    name: '',
    run() {},
    eat() {},
    cry() {}
}

4.3 接口继承类

接口把类的成员全部都抽象出来,只有类的成员结构,无具体的实现。

class Auto {          // 定义类
    state = 1
}
interface AutoInterface extends Auto {}          // 接口继承类,此时,接口中隐藏 state 属性
// 若想实现 AutoInterface 接口,只要类的成员有 state 属性即可
class C implements AutoInterface {
    state1 = 1
}
// Auto 的子类也可以实现 AutoInterface 接口
class Bus extends Auto implements AutoInterface {} 
// 因为 Bus 为 Auto 的子类,继承了 state 的属性

接口在抽离类的成员时,不仅仅抽离的公共成员,而且抽离了私有成员和受保护成员。如给 class Auto 添加 private state2 = 1时,那么 class 则会报错

关于类和接口的关系可总结如下:

上一篇下一篇

猜你喜欢

热点阅读