TS:接口的奥妙

TS: 接口的奥妙

1. 接口基础语法

interface Person{
    
    
    name : string
}

function sayhelloto(person : Person){
    
    
    console.log('hello,' + person.name);
}

let x = {
    
    
    name : 'yivi',
    age : 20
}
sayhelloto(x);	// hello,yivi

类型检查器不会去检查属性的顺序,只要相应的属性存在并且类型也是对应的就能够被识别。

2. 可选属性

  • 有时候,一些接口里的属性并不是必须的。只是在某些情况下需要存在,带可选属性的接口只需要在可选属性的后面加一个?即可。
interface Person {
    
    
    name : string;
    age : number;
    phone? : number     
}

3. 只读属性

  • 一些对象的属性只能在对象刚刚创建的时候修改其值,可在属性名前加上readonly来指定只读属性。
interface Point {
    
    
    readonly x : number;
    readonly y : number;
}

let p : Point = {
    
     x : 5, y : 5};
p.x = 190;	// error,只能读不能赋值
  • ts中具有ReadonlyArray<T>类型,它与Array<T>类似,只是把能够修改数组的方法去掉,因此可以确保数组创建后不被修改:
let a: number[] = [1,2,3,4];
let b: ReadonlyArray<number> = a;
b[0] = 2;	// error
b.push(4); 	// error
h.length = 20;	//error
a = b;	//error
//正确做法
a = b as number[];
  • 若作为变量,使用const
  • 若作为属性,使用readonly

4. 额外的属性检查

  • 在前面的例子中,我们传入了接口里没有的属性,结果是这个额外的属性被忽略了;但在有可选属性的接口中,这样做将会失败,提示额外的属性不在期望中。要绕开这个检查,使用类型断言即可。

    interface SquareConfig {
          
          
        color?: string;
        width?: number;
    }
    
    function createSquare(config: SquareConfig): {
          
           color: string; area: number } {
          
          
        // ...
    }
    
    let mySquare = createSquare({
          
           colour: "red", width: 100 } as SquareConfig);
    
  • 但最佳的方式是在接口里添加一个字符串索引签名。

    interface SquareConfig {
          
          
        color?: string;
        width?: number;
        [propName: string] : any;
    }
    

5. 函数类型

  • 接口除了能够描述带有属性的普通对象外,还可以描述函数类型;定义函数时,需要定义调用签名,是只有参数列表和返回值类型的函数定义。

    interface searchFunc {
          
          
        (source : string, subString: string) : boolean;
    }
    
    let mysearch : searchFunc;
    mysearch = function (source : string,subString : string) {
          
          
        let result = source.search(subString);
        return result > -1;
    }
    
  • 对于函数类型,函数的参数名不需要和接口里的一致。函数的参数会逐个进行检查,要求对应位置上的参数类型是兼容的。如果不指定类型,typescript会推断出参数类型。函数的返回值类型若与接口的不同,类型检查器会警告我们。

    mysearch = function(src : string,sub : string) : boolean {
          
          
        let result = src.search(sub);
        return result > -1;
    }
    
    // or
    
    mysearch = function(src , sub){
          
          
        let result = src.search(sub);
        return result > -1;
    }
    

6. 可索引的类型

  • 可索引类型具有一个索引签名,描述了对象索引的类型,还有相应的索引返回值类型。

    interface StringArray{
          
          
        [index : number] : string;
    }
    
    let myarr : StringArray;
    myarr = ["yivi","huang"];
    
    let mystr : string = myarr[0];
    
  • TypeScript支持两种索引签名:字符串和数字。 可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型。 这是因为当使用 number来索引时,JavaScript会将它转换成string然后再去索引对象。 也就是说用 100(一个number)去索引等同于使用"100"(一个string)去索引,因此两者需要保持一致。

    class Animal {
          
          
        name: string;
    }
    class Dog extends Animal {
          
          
        breed: string;
    }
    
    // 错误:使用数值型的字符串索引,可能会以字符串进行索引,有时会得到完全不同的Animal!
    interface NotOkay {
          
          
        [x: number]: Animal;
        [x: string]: Dog;
    }
    
  • 字符串索引签名能够很好的描述dictionary模式,并且它们也会确保所有属性与其返回值类型相匹配。

  • 因为字符串索引声明了 obj.propertyobj["property"]两种形式都可以。

    interface NumberDictionary{
          
          
        [index: string] : number;
        length : number;	// ok,返回值与索引返回类型一致
        name : string;		// error,string与索引返回类型不一致
    }
    
  • 同时,也可以讲索引类型设置为只读的。

7. 类

  • 与Java一样,typescript也能用接口来强制类必须实现的属性或方法。

    interface clockInterface{
          
          
        currentTime : Date;
        setTime(d : Date);
    }
    
    class Clock implements clockInterface{
          
          
        currentTime : Date;
        setTime(d : Date){
          
          
            this.currentTime = d;
        }
        constructor(h : number,m : number){
          
          }
    }
    
  • 类有两种类型:静态部分的类型和实例类型

  • 当用构造器签名去定义一个接口时并定义一个类去实现时,会报错。

  • 因为当一个类实现了一个接口时,只对其实例部分进行类型检查。 constructor存在于类的静态部分,所以不在检查的范围内。

    // 以下定义会报错
    // Class 'Clock' incorrectly implements interface 'ClockConstructor'.   Type 'Clock' provides no match for the signature 'new (hour: number, minute: number): any'.
    interface ClockConstructor {
          
          
        new (hour: number,minute : number);	// 静态类型
    }
    
    class Clock implements ClockConstructor{
          
          
        currentTime : Date;
        constructor(h: number,m:number);
    }
    
    // 正确示范
    interface ClockConstructor {
          
          
        new (hour: number, minute: number): ClockInterface;
    }
    interface ClockInterface {
          
          
        tick();
    }
    function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface {
          
          
        return new ctor(hour, minute);	// 返回类型为ClockInterface
    }
    
    class DigitalClock implements ClockInterface {
          
          
        constructor(h: number, m: number) {
          
           }
        tick() {
          
          
            console.log("beep beep");
        }
    }
    
    let digital = createClock(DigitalClock, 12, 17);
    
  • 因为createClock的第一个参数是ClockConstructor类型,在createClock(DigitalClock, 12, 17)里,会检查DigitalClock是否符合构造函数签名。

8. 继承接口

  • 和Java一样,typescript也同样支持接口的继承;
  • 一个接口可以继承多个接口;

9. 混合类型

  • typescript的好处是具有灵活动态的特点,一个对象可以同时作为函数和对象使用,而且具有额外的属性。

    interface Counter{
          
          
        (start : number) : string;
    	interval : number;
    	reset : void;
    }
    
    function getCounter() : Counter{
          
          
        let counter = <Counter>function (start : number){
          
          };
        counter.interval = 100;
        counter.reset = function(){
          
          };
        return counter;
    }
    
    let c = getCounter();
    c(10);
    c.reset();
    c.interval = 20;
    

10. 接口继承类

  • 当接口继承类时,他会继承类的成员但不包括其实现。意味着当你创建一个接口继承的类拥有私有或者保护变量时,这个接口只能被这个类或者其子类实现。

    class Control {
          
          
        private state: any;
    }
    
    interface SelectableControl extends Control {
          
          
        select(): void;	
    }
    
    class Button extends Control implements SelectableControl {
          
          
        select() {
          
           }	// ok,因为state为私有成员,所以只有子类才能实现SelectableControl接口
    }
    
    class TextBox extends Control {
          
          
        select() {
          
           }	
    }
    
    // 错误:“Image”类型缺少“state”属性。
    class Image implements SelectableControl {
          
          
        select() {
          
           }
    }
    
    
  • Control类内部,是允许通过SelectableControl的实例来访问私有成员state的。 实际上, SelectableControl接口和拥有select方法的Control类是一样的。

猜你喜欢

转载自blog.csdn.net/yivisir/article/details/109552136