typescript接口难点理解

TS总结

接口

1、对对象类型的检查

interface SquareConfig {
    color: string;
    width: number;
}

function createSquare(config: SquareConfig): { color: string; area: number } {
    // ...
}

let mySquare = createSquare({ color: "red", width: 100 });

2、对函数类型的检查

为了使用接口表示函数类型,我们需要给接口定义一个调用签名。 它就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每个参数都需要名字和类型。

interface SearchFunc {
  (source: string, subString: string): boolean;
}

这样定义后,我们可以像使用其它接口一样使用这个函数类型的接口。 下例展示了如何创建一个函数类型的变量,并将一个同类型的函数赋值给这个变量。

let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
  let result = source.search(subString);
  return result > -1;
}

!注意,对于函数类型的类型检查来说,函数的参数名不需要与接口里定义的名字相匹配。 比如,我们使用下面的代码重写上面的例子:

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

函数的参数会逐个进行检查,要求对应位置上的参数类型是兼容的。 如果你不想指定类型,TypeScript的类型系统会推断出参数类型,因为函数直接赋值给了 SearchFunc类型变量。 函数的返回值类型是通过其返回值推断出来的(此例是 false和true)。 如果让这个函数返回数字或字符串,类型检查器会警告我们函数的返回值类型与 SearchFunc接口中的定义不匹配。

3、可索引的类型

与使用接口描述函数类型差不多,我们也可以描述那些能够“通过索引得到”的类型,比如a[10]或ageMap[“daniel”]。 可索引类型具有一个 索引签名,它描述了对象索引的类型,还有相应的索引返回值类型。 让我们看一个例子:

interface StringArray {
  [index: number]: string;
}

let myArray: StringArray;
myArray = ["Bob", "Fred"];

let myStr: string = myArray[0];

上面例子里,我们定义了StringArray接口,它具有索引签名。 这个索引签名表示了当用 number去索引StringArray时会得到string类型的返回值。

TypeScript支持两种索引签名:字符串和数字。 可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型。 这是因为当使用 number来索引时,JavaScript会将它转换成string然后再去索引对象。 也就是说用 100(一个number)去索引等同于使用"100"(一个string)去索引,因此两者需要保持一致。 -------这句话其实应该是这样理解的:number索引是string索引的一种特殊情况。所以number索引的返回值也必须是基于string索引类型的返回值的。所以数字索引的返回值必须是字符串索引返回值类型的子类型。

class Animal {
    name: number;
}
class Dog extends Animal {
    breed: number;
}

// 错误:使用数值型的字符串索引,有时会得到完全不同的Animal!
interface NotOkay {
    [x: string]: Animal;
    [y: number]: Dog;
}

//顺序不同会报错
//interface NotOkay {
//    [x: string]: Animal;
//    [y: number]: Dog;
//}

let ins: NotOkay = { 'xiaohua': new Animal() };
let ins1: NotOkay = { 1: new Dog() }; //使用这种方法可以不报错
~~let ins1: NotOkay = [new Dog()];~~  //使用这种方法会报错,暂时搞不懂为什么,同样具有所因,不明白其中差异。

console.log(ins1[0].name);

最后,你可以将索引签名设置为只读,这样就防止了给索引赋值:

interface ReadonlyStringArray {
    readonly [index: number]: string;
}
let myArray: ReadonlyStringArray = ["Alice", "Bob"];
myArray[2] = "Mallory"; // error!

4、类类型
实现接口

与C#或Java里接口的基本作用一样,TypeScript也能够用它来明确的强制一个类去符合某种契约。

interface ClockInterface {
    currentTime: Date;
}

class Clock implements ClockInterface {
    currentTime: Date;
    constructor(h: number, m: number) { }
}

类静态部分与实例部分的区别
当你操作类和接口的时候,你要知道类是具有两个类型的:静态部分的类型和实例的类型。 你会注意到,当你用构造器签名去定义一个接口并试图定义一个类去实现这个接口时会得到一个错误:

interface ClockConstructor {
    new (hour: number, minute: number);
}
//错误的原因:因为类对接口的实现,就说明了类的实例会有什么,new ()这个表达式表示一个可以被new的东西,也就表示他是一个构造函数,
//因为一个类的实例不会包含构造签名,所以它不能满足这个接口的要求;
class Clock implements ClockConstructor {
    currentTime: Date;
    constructor(h: number, m: number) { }
}
//这个例子比较好理解一些
interface ComesFromString {
    name: string;
}

interface StringConstructable {
    new(n: string): ComesFromString;  //new(n: string)仅仅代表一个可以new的签名
}

class MadeFromString implements ComesFromString {
    constructor(public name: string) {
        console.log('ctor invoked');
    }
}

function makeObj(n: StringConstructable): ComesFromString {
    return new n('hello!');  //new之后是一个ComesFromString类型的值
}

console.log(makeObj(MadeFromString).name);

看一个来自官方的例子

interface ClockConstructor { 
    new (hour: number, minute: number): ClockInterface;  //代表实例以后得到一个返回值ClockInterface的对象
}
interface ClockInterface {
    tick();
}

function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface {
    return new ctor(hour, minute);
}

class DigitalClock implements ClockInterface {
    constructor(h: number, m: number) { }
    tick() {
        console.log("beep beep");
    }
}
class AnalogClock implements ClockInterface {
    constructor(h: number, m: number) { }
    tick() {
        console.log("tick tock");
    }
}

let digital = createClock(DigitalClock, 12, 17);
let analog = createClock(AnalogClock, 7, 32);

更正式地说,类对接口的实现就是在说明这个类的实例会有什么。因为一个类的实例不会包含构造签名,所以它不能满足这个接口的要求

总结一下就是 ,TypeScript中不能实现一个带有构造函数的接口,但可以通过继承另一个接口,并让包含构造函数的接口返回同样的类型,在类中就能实现对该构造函数的类型检查。

再精简一下,如果我们把包含构造函数的接口叫做静态部分,把类实现的叫做实例部分,即类只能实现实例部分,而静态部分则可以通过传参进行类型检查

继承接口(略:主要讲难理解的)

接口继承类
当接口继承了一个类类型时,它会继承类的成员但不包括其实现。 就好像接口声明了所有类中存在的成员,但并没有提供具体实现一样。 接口同样会继承到类的private和protected成员。 这意味着当你创建了一个接口继承了一个拥有私有或受保护的成员的类时,这个接口类型只能被这个类或其子类所实现(implement)。

当你有一个庞大的继承结构时这很有用,但要指出的是你的代码只在子类拥有特定属性时起作用。 这个子类除了继承至基类外与基类没有任何关系。 例:

class Control {
    private state: any;
}

interface SelectableControl extends Control {
    select(): void;
}

class Button extends Control implements SelectableControl {
    select() { }
}

class TextBox extends Control {
    select() { }
}

// 错误:“Image”类型缺少“state”属性。
// 这意味着当你创建了一个接口继承了一个拥有私有或受保护的成员的类时,这个接口类型只能被这个类或其子类所实现(implement)。
// 如果父类是一个拥有protected的属性,有一些用,但如果是private,看起来好像没有什么意义,因为子类无法得到那个类的私有变量
class Image implements SelectableControl {
    select() { }
}

class Location {

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值