美文网首页
Typescript - 基础(三)

Typescript - 基础(三)

作者: 酷热summer | 来源:发表于2020-05-03 18:27 被阅读0次

    接口、函数、类

    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 就会对额外的参数进行类型检查。绕过这种检查的方法一共有三种:

    • a. 如上,将对象字面量赋值给变量
    • b.使用类型断言
    // 类型断言的含义是,告诉编译器,我们使用的类型是 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'}
        ]
    })
    
    • c: 使用字符串索引签名
    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 可索引类型接口

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

    • a.数字索引的接口
    interface StringArray {
      [index: number] : string;
    }
    // 用任意的数字去索引 StringArray 都可以得到一个 string,相当于声明了一个字符串数组
    let arr: StringArray = ['a', 'b'];
    
    • 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 类的成员修饰符

    • public : 公有成员,类的所有属性默认都是 public,对所有人都是可见的,上述的类可写为:
    class Dog {
      constructor(name: string) { 
        this.name = name;
      }
      public name: string;
      public run () {};
    }
    
    • private: 私有成员,只能在类的本身被调用,不能被类的实例调用,也不能被子类调用
    class Dog {
      constructor(name: string) { 
        this.name = name;
      }
      public name: string;
      public run () {};
      private fn () {}
    }
    let dog = new Dog();
    dog.fn();       // 此处会报错,子类同理
    

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

    • protected: 受保护成员,只能在类或者子类中被访问,而不能在实例中被访问
      构造函数也可被声明为 protected,表示这个类不能被实例化,只能被继承。
    • readonly: 只读属性,属性不可被更改,且必须要初始化。
    • 构造参数的成员也可以添加修饰符,作用:将参数自动变成实例的属性
    // 此处的 class 定义跟直接定义 name 属性的效果一致
    class Dog {
      constructor(public name: string) { 
        this.name = name;
      }
    }
    
    • static 修饰符: 类的静态成员,只能通过类名来调用
    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)口必须具有以下特性:

    • 类实现接口的时候,必须声明接口中所有的属性
    • 接口只能约束类的公有成员,如果将类中的 name 声明为 private name,那么将报错
    • 接口不能约束类的构造函数

    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 则会报错

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

    • 接口之间可以相互继承,extends
    • 类之间可以相互继承, extends
    • 接口可以通过类实现,接口只能约束类的公有成员, implements, public
    • 接口可以抽离类的成员,抽离的成员包括:公有/私有/受保护成员(public/private/protected)

    相关文章

      网友评论

          本文标题:Typescript - 基础(三)

          本文链接:https://www.haomeiwen.com/subject/paodghtx.html