乐趣区

TypeScript自学第三章接口

接口

在 JavaScript 中没有这个概念。

接口初探

function printLabel(labelledObj: { label: string}) {console.log(labelledObj.label);
}
// label 为传入对象参数必有的一个属性
let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);

下面利用接口重写例子

// 个人理解为将传入参数要求提出来,看起来更加清晰
interface LabelledValue {label: string;}

function printLabel(labelledObj: LabelledValue) {console.log(labelledObj.label)
}

let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);

还有一点值得提的是,类型检查器不会去检查属性的顺序,只要相应的属性存在并且类型也是对的就可以。

可选属性

接口里的属性不全都是必需的。

interface SquareConfig {
  color?:string; // 注意这里是分号哟
  width?:number;
}

function createSquare(config:SquareConfig):{color:string;area:number}{let newSquare = {color: "white", area: 100};
  if (config.color) {newSquare.color = config.color;}
  if (config.width) {newSquare.area = config.width * config.width;}
  return newSquare;
}
let mySquare = createSquare({color: "black"});

只读属性

只读属性创建后不能再次修改。

interface Point {
    readonly x: number;
    readonly y: number;
}

let p1: Point = {x: 10, y: 20};
p1.x = 5; // error!

下面的代码可以让 ro 数组创建后再也无法改变

let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // error!
ro.push(5); // error!
ro.length = 100; // error!
a = ro; // error!
a = ro as number[]; //right

readonly VS const
官方解说:最简单判断该用 readonly 还是 const 的方法是看要把它做为变量使用还是做为一个属性。做为变量使用的话用 const,若做为属性则使用readonly

额外的属性检查

可以漏写部分属性,但不能多写属性或者错写属性,即如果传入的对象中含有目标类型不存在的属性,TS 额外的属性检查机制会进行报错。
官方提供了绕开 TS 的额外属性检查的办法:

// 1. 使用类型断言
let mySquare = createSquare({width: 100, opacity: 0.5} as SquareConfig);

// 2. 声明接口时来一个任意类型,此时这个声明为该接口的对象,可以有任意数量、任意类型、只要名字不是 color 或 width 的属性
interface SquareConfig {
  color?: string;
  width?: number;
  [propName: string]: any;
}

// 3. 更简单,只要把对象赋值到另一个变量上(抽离出来)即可:let squareOptions = {colour: "red", width: 100};
let mySquare = createSquare(squareOptions);

个人感觉不要使用,毕竟这个是 TS 来帮我们检查 BUG 的。

函数类型

使用接口表示函数类型

// 像是一个只有参数列表和返回值类型的函数定义。参数列表里的每个参数都需要名字和类型。interface SearchFunc {(source:string, subString:string):boolean
}

使用方法

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

当然可以省略部分代码

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

可索引类型

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

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

let myStr: string = myArray[0];

这个索引签名表示了当用 number去索引 StringArray 时会得到 string 类型的返回值。

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

interface NotOkay {[x: number]: Animal;// 此处错误
  [x: string]: Dog;
}

因为当使用 number来索引时,JavaScript 会将它转换成 string 然后再去索引对象。也就是说用 100(一个 number)去索引等同于使用"100"(一个string)去索引。
记住一句话:数字类型索引的返回值必须是字符串类型索引返回值的子类型。这里 AnimalDog的父类型,所以错误。

此外,字符串索引签名能够很好的描述 dictionary 模式,并且它们也会确保所有属性与其返回值类型相匹配。
如:

interface NumberDictionary {[index: string]: number;
  length: number;    // 可以,length 是 number 类型
  name: string       // 错误,`name` 的类型与索引类型返回值的类型不匹配
}
// 这里一知半解不太明白是什么意思

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

interface ReadonlyStringArray {readonly [index: number]: string;
}

类类型

这一部分等看了 TS 中的类以后再回头来看

继承接口

interface Shape {color: string;}

interface Square extends Shape {sideLength: number;}

let square = <Square>{};
square.color = "blue";
square.sideLength = 10;

这样 square 的 2 个参数都要受限制了
一个接口可以继承多个接口,创建出多个接口的合成接口。

interface Shape {color: string;}

interface PenStroke {penWidth: number;}

interface Square extends Shape, PenStroke {sideLength: number;}

混合类型

这个也留着以后回头来看

接口继承类

同上

退出移动版