Typescript 实战 --- (3)接口

接口可以用来约束对象、函数以及类的结构和类型,这是一种代码协作的契约,调用该接口时必须遵守,不能改变
 
ts与其它语言不同的是,并没有 “实现” 接口这个概念,而是只关注值的外形,只要传入的对象满足上面的必要条件,那么它就是被允许的。这就是所谓的“鸭式辩型法”(像鸭子一样走路、游泳和嘎嘎叫的鸟就是鸭子)
 
1、对象类型接口
 
interface List {
  id: number;
  name: string;
}

interface Result {
  data: List[];
}

function getResult(result: Result) {
  result.data.forEach(item => {
    console.log(item.id, item.name);
  })
}

let myResult = {
  data: [
    {id: 1, name: 'Bob', score: 98}, 
    {id: 2, name: 'Carl', score: 87}
  ] 
};

getResult(myResult);

// 1 'Bob'
// 2 'Carl'
 
类型检查器会查看 getResult 的调用,其传入的参数中包含了三个属性,但是编译器只会检查那些必需的属性是否存在,并且其类型是否匹配
 
然而,有些时候ts却并不会这么宽松,例如:getResult 参数传入的是对象字面量,ts就会对额外的参数进行类型检查
getResult({ data: [{id: 1, name: 'Bob', score: 98}, {id: 2, name: 'Carl', score: 87}] });


// error TS2322: Type '{ id: number; name: string; score: number; }' is not assignable to type 'List'.
// Object literal may only specify known properties, and 'score' does not exist in type 'List'.
 
绕过这种检查的方法有四种:
  (1)、把对象字面量赋值给一个变量(如前面的例子中的变量 myResult)
  (2)、使用类型断言
  (3)、可选属性:带有可选属性的接口与普通的接口定义差不多,只是在可选属性名字定义的后面加一个?符号
  (4)、字符串索引签名
 
// 使用类型断言
getResult({data: [{id: 1, name: 'Bob'}, {id: 2, name: 'Carl', score: 98}]} as Result)

// 可选属性
interface List {
  id: number;
  name: string;
  score?: number;  // 可选属性
}

// 字符串索引签名
interface List {
  id: number;
  name: string;
  [propName: string]: any; // 字符串索引签名
}

getResult({ 
  data: [{id: 1, name: 'Bob', score: 98}, {id: 2, name: 'Carl', score: 87}] 
});


// 1 'Bob'
// 2 'Carl'
 
1-1、可索引类型
可索引类型具有一个索引签名,它描述了对象索引的类型,还有相应的索引返回值类型。比如:a[0] 或 a["name"]
索引签名可以是字符串和数字,也可以同时使用两种类型的索引
 
// 数字索引
interface StringArray {
  [inder: number]: string;
}

let s1: StringArray = ["TypeScript", "Webpack"];
console.log('s1: ', s1);   // s1:  [ 'TypeScript', 'Webpack' ]

// 字符串索引
interface ScoreMap {
  [subject: string]: number;
}

let s2: ScoreMap = {
  "Chinese": 99,
  "Math": 100
}
console.log('s2: ', s2);   // s2:  { Chinese: 99, Math: 100 }

// 同时使用字符串和数字索引
interface StudentMap {
  [index: number]: string;
  [name: string]: string;
} 

let s3: StudentMap[] = [
  {
    1: "678分",
    "姓名": "张伟"
  },
  {
    2: "670分",
    "姓名": "尔康"
  }
]
console.log('s3: ', s3);
// s3:  [ { '1': '678分', '姓名': '张伟' }, { '2': '670分', '姓名': '尔康' } ]
 
注意:如果同时使用字符串索引和数字索引,数字索引的返回值必须是字符串索引返回值类型的子类型。因为当使用number来索引时,js会将它隐式转换成string,然后再去索引对象。
 
class Animal {
  name: string;
}

class Dog extends Animal {
  breed: string;
}

interface Okay {
  [x: string]: Animal;
  [y: number]: Dog;
}

// Numeric index type 'Animal' is not assignable to string index type 'Dog'.
interface NotOkay {
  [x: string]: Dog;
  [y: number]: Animal;  // 数字索引类型“Animal”不能赋给字符串索引类型“Dog”
}

 

1-2、只读属性

可以在属性名前用 readonly 来指定只读属性

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

// 可以通过赋值一个对象字面量来构造一个Point。 赋值后,x和y再也不能被改变了。
let p: Point = { x: 3, y: 5};
console.log('p', p);   // p { x: 3, y: 5 }

// p.x = 20;   // Cannot assign to 'x' because it is a read-only property

ts 具有 ReadonlyArray<T> 类型,它与 Array<T> 相似,只是把所有可变方法都去掉了。可以确保数组创建后就再也不能修改

let arr: number[] = [1, 2, 3];
let ro: ReadonlyArray<number> = arr;

ro[0] = 33;   // 类型“readonly number[]”中的索引签名仅允许读取
ro.push(4);   // 类型“readonly number[]”上不存在属性“push”
ro.length = 99;  // Cannot assign to 'length' because it is a read-only property

判断该使用readonly 还是 const 的方法主要是看作为变量还是作为属性,作为变量的话使用 const,作为属性则使用 readonly

 

2、函数类型接口

它就像是一个只有 参数列表 和 返回值类型 的函数定义,参数列表里的每个参数都需要名字和类型
interface Add {
  (base: number, increment: number): number
}

// 调用接口
let add: Add = (x: number, y: number) => x + y;
console.log( add(1, 2) );   // 3

 

3、类类型接口
 
ts中的类可以像Java里的接口一样,使用类来实现一个接口,由此来明确的强制一个类去符合某种契约。
interface ClockInterface {
  currentTime: Date;
}

class Clock implements ClockInterface {
  currentTime: Date;
  constructor(h: number, m: number) {}
}
 
也可以在接口中描述一个方法,在类里实现它
interface ClockInterface {
  currentTime: Date;
  setTime(d: Date):
}

class Clock implements ClockInterface {
  currentTime: Date;
  setTime(d: Date) {
    this.currentTime = d;
  }
  constructor(h: number, m: number) {}
}

 

4、混合类型接口
 
同时使用 对象类型接口 和 函数类型接口
interface Lib {
  (): void;
  version: string;
  doSomething(): void;
}

function getLib() {
  let lib = (() => {}) as Lib;
  lib.version = '0.01';
  lib.doSomething = () => console.log('hello world!');
  return lib;
}

let l1 = getLib();
console.log( l1() );  // undefined

let l2 = getLib();
l2.doSomething();     // hello world!
 
5、继承接口
 
接口也可以相互继承,可以从一个接口里复制成员到另一个接口里,由此可以更加灵活的将接口分割到可重用的模块里
interface Shape {
  color: string;
}

interface Square extends Shape {
  sideLength: number;
}

let square: Square = {
  color: 'red',
  sideLength: 15
}

console.log('square ', square);   // square  { color: 'red', sideLength: 15 }
 
一个接口可以继承多个接口,创建出多个接口的合成接口
interface Shape {
  color: string;
}

interface PenStroke {
  penWidth: number
}

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

let square: Square = {
  color: 'red',
  penWidth: 6,
  sideLength: 15
}

console.log('square ', square);   // square  { color: 'red', penWidth: 6, sideLength: 15 }

 

 

 
posted @ 2020-01-14 11:16  rogerwu  阅读(734)  评论(0编辑  收藏  举报