TypeScript相关知识点整理_接口

141 阅读8分钟

接口interface

定义接口类型———定义特定形状的对象类型
接口重复定义会合并

接口结构概览

interface Person {
    readonly id: number,
    name: string,
    age?:number,
    sayHi():string, //类型为方法,返回string
    doSomething:()=>void //类型为方法,返回void
}
  • 使用 readonly 关键字 设置属性 初始化后 不可变
  • 使用 ? 设置 可选属性(结构中 可存在 也 可忽略 的属性)
  • 接口 方法结构 的定义有如上: sayHi()doSomething两种

函数类型注解的接口 和 其他函数类型的定义区别

  • 利用interface对函数类型进行注解 区别于 函数声明的注解方式,后者注解类型同时包含函数实现,interface仅注解函数类型,解耦了类型和实现,且比 函数表达式的写法 更简洁清晰
  • 共有3种 函数类型定义 方式以及对 匿名函数的重命名:函数表达式,函数声明,interface,类型别名
        //1.接口表示的函数类型注解
    {
        interface SearchFunc {  //注解函数类型
            (source: string, subString: string): boolean;
        }
    
        let mySearch: SearchFunc;   //定义函数类型
        mySearch = function(source: string, subString: string) {  //函数实现
          let result = source.search(subString);
          return result > -1;
        }
    }
    
        //2.函数声明:包括实现
    {
        function test(a:number, b:number):number {
            return a + b
        }
        test(1,2)
    
        function funTest(a:number):number {
            return a
        }
    }
    
        //3.函数表达式
    {
        let test = function(a:number, b:number):number {     //非正式:注解和实现杂糅
            return a + b
        }
    
        let test1: (a:number, b:number) => void = () => {    //正式注解,此处冒号后的箭头不表示es6函数,表示类型注解,等号后的箭头函数才表示实现
            console.log('hi');
    
        }
    
        let test2: (a:number, b:number) => number = (a,b) => {   //冒号后为表达式的正式注解
            return a + b
        }
    
    }
    
        //4.类型别名:本质是用提取函数类型声明的部分取名
    {
        type SearchFunc = (source: string, subString: string) => boolean;  //给函数类型声明补充名
    
        let mySearch: SearchFunc;   //赋予函数类型
        mySearch = function(source: string, subString: string) {  //函数实现
          let result = source.search(subString);
          return result > -1;
        } 
    }
    
    

混合类型

  • 函数类型接口内还有其他属性的类型结构:函数类型+对象类型
    • 一种变量的封装,定义一个函数用法,同时希望定义不污染全局的变量,用接口实现这样的结构
    interface Counter {
        (start: number): string;   //函数类型
        interval: number;       //对象类型--属性
        reset(): void;          //对象类型--方法
    }
    
    function getCounter(): Counter {
        let counter = <Counter>function (start: number) { };   //类型断言
        counter.interval = 10;
        counter.reset = function () { };
        return counter;
    }
    
    • 只有接口的混合类型可以将函数类型及属性作为一组结构成组,原始的js函数类型无法定义内部属性,只能设置内部变量
    let obj = function() {var test0 = 0};
    obj.test = 0   //报错
    

类型别名 和 接口

  • 类型别名 是对匿名或已存在类型的重命名,并理解为定义新类型,而 接口 能定义 对象结构的类型
  • 类型别名 不会出现在编写提示中,而是会采用别名前的原始类型进行提示
  • 类型别名 通常是用来对联合类型等复杂的类型进行简化命名的作用
    //接口注解函数
    interface FunInterface {
        (a:number, b:number):void
    }
    //给类型+赋值
    let fun: FunInterface = (a, b) => {
        console.log('funtype',typeof fun);
    }

    //类型注解--可以理解为为某种类型取名,而不是定义类型
    type FunType = (a:number, b:number) => void;
    type Fun = FunInterface;

    let fun1: FunType = (a, b) => {
        console.log('fun1type',typeof fun1);
    }
    let fun2: Fun = (a, b) => {
        console.log('fun2type',typeof fun2);
    }
    
    //类型别名用来简化类型声明
    type multyType = number | string | boolean;

接口的 继承 及 被实现

extends继承 接口

  • 接口只具有类型结构,继承可以有多个,最终都是类型结构的融合

extends继承 类

  • 类也具有类型结构,可被接口继承。
  • 接口会继承到类成员的可访问性及读写性,一个接口继承了一个拥有protectedprivate成员的类时,这个接口类型只能被这个类的派生类所实现,无法作为类型结构定义变量类型

可访问修饰符下的 类继承

```js
//具备私有属性的类
class Control {
    private state: any;
}

//继承具备私有属性类的接口
interface SelectableControl extends Control {
    select(): void;
}

//继承了基类的派生类
class ControlChild extends Control implements SelectableControl {
    select() {
        console.log('继承了基类的派生类可以 实现 继承了基类的接口');
    }
}

//报错,由于该接口继承了有私有属性的类,无法被外部赋值调用
let sel0: SelectableControl = {
   select() {},
}  
```

静态/读写修饰符下的 类继承

  • 静态修饰符会将属性局限在类的内部,接口继承也无法获取,只读修饰符会让接口继承的成员类型也只读
    class TestClass {
        static inner: string;
        static namedInner = 'named';
        readonly _inner: string
    }
    
    interface InterTest extends TestClass {}
    
    let intertest: InterTest = {
        _inner: 'inner'
    }
    intertest._inner = '1';  //error,只读属性
    console.log(TestClass.inner, TestClass.namedInner);  //undefined 'named'
    console.log(intertest); //不具备class的静态属性
    
  • 不论是接口还是派生类,都不能违背修饰符的特性,私有成员无法被接口或派生类继承,同样继承了该类的接口 被 该类的派生类实现时,也无法实现私有成员
    class Private {
        protected name: string;
        private name0: string;
    }
    interface Normal extends Private {
        name: 'hi';
        // name0: 'hi';   //error 私有不可被继承
    }
    class Normal implements Private {
        name: 'hi';
        // name0: 'hi';   //error 私有不可被实现
    }
    

抽象类 和 接口 的区别

  • 抽象类可以混合 具体方法属性以及 需要被实现的 抽象方法属性

  • 抽象类 为了强制其派生类 实现 某些功能

    abstract class Class0 {
      abstract method1(): void;
      abstract property1: number;
      methods() {
        console.log('methods')
      }
      property2 = 2;
    }
    
  • 接口只有结构,必须被实现

可索引类型接口

可索引类型以键值对形式表示,表示支持key查询value
在ts中属于类型操作符(type manipulation operator)

  • 索引类型接口的表示方式
    class Animal {
        name: string;
    }
    class Dog extends Animal {
        type: string;
    }
    
    interface AnimalTest {
        [random: number]: Dog;   //任意属性名的签名可以随意标记
    }
    let animal: AnimalTest = {
        0: {
            name: 'dog',
            type: 'Chihuahua',
        }
    }
    
  • 当接口中用任意属性名的方式定义了索引类型后,其他具体属性类型必须符合此索引类型
    interface NumberDictionary {
         [index: string]: number;  //最大包含
         length: number;    // 可以,length是number类型
         name: string       // 错误,`name`的类型与索引类型返回值的类型不匹配
    }
    

扩展

  • 接口的类型注解和类一样也能用 只读修饰符readonly 限制类型的读写性,表示该属性只能一次赋值;不同的是 类作为构造函数使用时的readonly属性无法被实例对象进行赋值,只能内部赋值否则就是undefined
    interface ReadInter {
        readonly innerName: string
    }
    let readinter: ReadInter = {
        innerName: 'first'
    }
    // readinter.innerName = 'second'; //error,不可二次赋值
    
    class ReadClass {
        readonly innerName: string;
        readonly innerString = 'inner'
    }
    let readclass: ReadClass = {
        innerName: 'first',
        innerString: 'inner'
    }
    let readclassIns = new ReadClass();
    // readclassIns.innerName = 'first';  //error,实例对象不可赋值
    console.log(readclassIns.innerName);  //undefined
    
    // readclass.innerName = 'second';  //error,不可二次赋值
    
  • 可索引类型操作符 还可以把已定义的数组按照元素进行类型获取,配合typeof能够生成数组所有元素的联合类型
     const MyArray = [
         { name: "Bob", age: 23 },
         { name: "Bob", age: 23, sex: 'string'},
     ];
    
       type Person = typeof MyArray[number];  
    
       //等价于
       type Person = {
           name: string;
           age: number;
           sex?: undefined;
       } | {
           name: string;
           age: number;
           sex: string;
       }
    
  • 当被索引的类型正好是可索引类型时,直接用keyof遍历其键得到的就是所有键对应的值(类型)
    type Person = { age: number; name: string }; 
    
    type PersonUnion = Person[keyof Person];  
    //type PersonUnion = string | number
    
  • 实现 接口,接口 继承 类的区别
    • 接口是一种类型结构,不是某种功能的具体实现;
    • 类是把相同特性的一些实例抽象为一个整体,若非抽象类,则有具体实现;
    • 因此:类可以实现接口的具体功能,而接口只能继承类中类型结构的部分
      interface SomeApi {
          name: string;
          tiktok: ()=>void
      }
      
      //implements
      class Clock implements SomeApi {
          name: 'api';
          tiktok: () => {console.log('tik')}
      }
      //实现每个属性方法都必须和需要实现的接口一致
      
      //extends method
      interface SomeApi2 extends SomeApi {
          tiktok: () => number
      }
      //error: extends prop
      interface SomeApi2 extends SomeApi {
          name: number
      }
      //继承不可以修改父接口/父类的属性,但可以修改方法-(多态体现)
      
  • 接口 和 类 的继承,实现之间的关系

截屏2023-01-11 06.34.06.png

  • 类型断言
    • 作用:可以绕开定义变量的类型限制,如对象类型不用满足所有对象属性的格式
    • 语法结构:
      注意断言和泛型的使用区别,断言<someType>类型在前,泛型<someType>在后
      let someValue: any = "this is a string";
      
      //尖括号语法
      let strLength: number = (<string>someValue).length;
      
      //as语法
      let strLength: number = (someValue as string).length;
      
    • 使用示例:
      interface Shape {
          color: string;
      }
      
      interface Square extends Shape {
          sideLength: number;
      }
      
      
      //类型声明并赋值--必须保证赋值满足类型限制,如果在声明对象的结构没有符合类型检查而对单个属性进行赋值则会报错
      let square:Square;  
      //square.color = 'blue'   //报错
      square = {
          sideLength: 10,
          color: 'red'
      };  //正确,必须完全满足类型结构
      
      //类型断言--告诉检查程序一定会是这个类型,程序不会进行检查,因此可对单个属性进行赋值,但可能赋值过程就会遗漏
      let square1 = <Square>{};
      square1.color = "blue";
      console.log(square1);   //{color: 'blue'}
      
  • 当某个变量的形状不符合类型结构
    • 变量的类型结构必须完全符合,否则报错
    interface Test {
       str: string
    }
    
    let test0: Test = {
       str: 'str',
       num: 0  //报错
    }
    
    • 函数传参的类型结构只要具备必要参数就不会报错
    interface Test {
       str: string
    }
    
    function test0(test: Test):void {}
    
    let test1 = {
       str: '1',
       num: 0
    }
    test0(test1)
    
  • 命名冲突的情况
    • 接口同名:合并声明结构
        interface Foo {
          x: number;
        }
        // ... elsewhere ...
        interface Foo {
          y: number;
        }
        let a: Foo = {x:1,y:1};  
        console.log(a.x + a.y); // OK
      
    • 类或接口同名,只会根据后续声明必须和前置声明原则一致去规范,不论接口还是类,不论先继承还是实现,都遵循此原则