登录/注册
开开
5721
占位
8
占位
11
浏览量
占位
粉丝
占位
关注
TypeScript-接口类型
开开
2020-12-07 17:15:59
132
0

对象类型

对象类型是TypeScript的类型系统中最复杂也是最重要的类型,对象类型主要用来描述复杂数据类型:

// 声明一个值为对象字面量
let man = {name: 'joye', age: 30};

// 等价于
let man: {name: string; age: number} = {name: 'joye', age: 30};

在上例第一条语句中,实际上变量 man 会被自动推导为类型 {name: string; age: number},它描述了一个对象具有字符串类型的 name 属性和数字类型的 age 属性 。

感谢wenxudong090161 提醒:属性之间可以用分号;分隔,也可以用逗号,分隔,甚至可以只用换行符分隔! 参考Issues #1。为了保持代码的可读性和规范,建议声明的描述中采用;分隔,以此来区分注解和值

接口类型

对象类型是匿名的接口类型,对象类型没有名字,接口类型有名字。接口类型相当于为对象类型声明了一个别名

// 定义接口类型Person
interface Person {
  name: string;
  age: number;
}

// 声明变量 man 为 Person 接口类型
let man: Person = {name: 'joye', age: 30};

上述语句完全等价于:

let man: {name: string; age: number} = {name: 'joye', age: 30};

本教程后续章节将统一术语:接口代表接口类型匿名接口代表对象类型

可选属性

接口的属性是可选的,可选属性类似于函数的可选参数:属性名后添加问号?即可

interface Person {
  name: string;
  age: number;
}

// 错误,缺少必选属性 age
// error TS2322: Type '{ name: string; }' is not assignable to type 'Person'. 
// Property 'age' is missing in type '{ name: string; }'.
let man: Person = {
  name: 'joye'
};

age 改成可选属性:

interface Person {
  name: string;
  // 注意此处的问号,age此时为可选属性
  age?: number; 
}

// 正确
let man: Person = {
  name: 'joye'
};

只读属性

可以通过在属性名前添加 readonly 关键字来指定只读属性,只读属性只能在创建的时候对其赋值,一旦创建完成,就再也不能更改:

interface Person {
  // 声明name为只读
  readonly name: string;
  age: number;
}
// 创建时对只读属性赋值
let man: Person = {
  name: 'joye',
  age: 30
}

// 错误,只读属性的值不能更改
// error TS2540: Cannot assign to 'name' because it is a constant or a read-only property
man.name = 'mike';

// 正确,非只读属性的值可以更改
man.age = 31;

接口的应用

接口最重要的作用在于描述一个复杂值的外形,通常情况下,接口可以描述:

  • 对象字面量
  • 函数
  • 可索引值

描述对象字面量

前面的 Person 接口就是描述对象字面量的例子,此处不再重复举例。

对象字面量的类型匹配非常让人迷惑,请看下面的例子:

interface Person {
  name: string;
  age: number;
}

// 定义一个对象字面量male
let male = {
  name: 'joye',
  age: 30,
  gender: 'male'
};

// 正确,male包含Person接口的所有属性
let man: Person = male;

在上面的例子中,对象字面量 male 被编译器推导为匿名接口类型,相当于:

// 声明male为匿名接口
let male: {
    name: string;
    age: number;
    gender: string;
};
// 对male赋值
male = {
  name: 'joye',
  age: 30,
  gender: 'male'
};

匿名接口类型包含了 Person 接口的所有属性 nameage,编译器认为类型匹配,通过类型检查。然而:

interface Person {
  name: string;
  age: number;
}

// 直接将对象字面量赋值给接口类型
// 错误,对象字面量直接赋值检查所有属性的兼容性
// error TS2322: Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'. 
// Object literal may only specify known properties, and 'gender' does not exist in type 'Person'.
let man: Person = {
  name: 'joye',
  age: 30,
  gender: 'male'
};

请牢记:对象字面量在直接赋值的时候,编译器会检查字面量类型是否完全匹配,多一个或少一个属性都会报错。

描述函数

声明一个函数类型的多种方式:

// 描述函数
interface MyFunc {
  (name: string, age: number): string; 
}

// 声明接口类型
let fn: MyFunc; 
// 等价于
let fn: { (name: string, age: number): string; } // 匿名接口
// 等价于
let fn: (name: string, age: number) => string; 


// 赋值
fn = function(name: string, age: number): string {
  return `${name}, ${age}`;
}

例子,用接口描述一个带静态属性的函数:

// 定义myFunc 函数
function myFunc(){}
// myFunc具有静态属性 `test`
myFunc.test = 'hello world';

// 声明接口类型描述函数 myFunc
interface MyFunc {
  // 这条语句描述函数定义
  (): void;
  // 这条语句描述静态属性 `test`
  test: string;
}

// 正确,类型匹配
let newFunc: MyFunc = myFunc;

描述可索引值

可索引值一般表示数组类型和对象类型,可以通过键访问某一项的值或属性值。

描述数组:

// 描述一个数组
interface StringArray {
  [index: number]: string;
}

// 声明接口类型
let myArray: StringArray;
// 等价于
let myArray: { [index: number]: string; }; // 匿名接口
// 等价于
let myArray: string[];

// 赋值
myArray = ["Bob", "Fred"];

描述对象:

// 描述一个对象
interface MyObject {
  [index: string]: string;
}

// 声明接口类型
let myObject: MyObject;

// 赋值
myObject = {
  a: '1', b: '2', c: '3'
}

对比前面描述对象字面量的语法,你会发现,这种方式描述对象可以支持无限多的对象属性,上述例子中:

// 省略号代表其他任意属性
myObject = {
  a: '1', b: '2', c: '3', d: '4', ...
}

描述类数组对象

如果一个对象既支持数字索引,也支持字符串索引,这种对象在JavaScript中被称作类数组对象:

// 类数组对象
let obj = {
  1: 1,
  2: 2,
  name: 'joye',
  age: 30
}

obj[1] === 1;
obj[2] === 2;
obj['name'] === 'joye';
obj['age'] === 30;

实际上,当采用数字索引方式访问一个值时,JavaScript会将数字索引转换为字符串索引:

obj[1] === 1;
obj[2] === 2;

// 完全等价于
obj["1"] === 1;
obj["2"] === 2;

在TypeScript中,接口类型可以同时描述数字索引类型和字符串索引类型:

// 正确
interface IndexObj {
    [x: number]: string;
    [x: string]: string;
}

但要注意,由于JavaScript会将数字索引转换为字符串索引,数字索引和字符串索引的值的类型必须相等,或者数字索引的返回值必须是字符串索引返回值类型的子类型:

// 错误,数字索引的值和字符串索引的值不匹配
// error TS2413: Numeric index type 'number' is not assignable to string index type 'string'
interface IndexObj {
    [x: number]: number;
    [x: string]: string;
}

描述类

类类型 章节的构造器类型讲解中可以知道,构造器类型代表的就是类本身。用接口来描述一个类:

// 定义一个类
class NewClass {}

// 用接口来描述这个类类型
interface MyClass {
  new(): NewClass;
}

// 声明一个变量为描述这个类的接口类型并初始化
let myClass: MyClass = NewClass;
// 等价于
let myClass: typeof NewClass = NewClass;

用类来实现接口

我们介绍到用接口来描述函数、可索引值、类类型,你会发现还不如直接用类型来声明更直接:

// 声明函数
let myFunc: ()=>{};

// 声明数组
let myArr: string[];

// 声明类
class MyClass {}
let myClass: typeof MyClass;

在实际使用中的确是这样,我们很少直接用接口来声明一个函数或数组。接口更重要的场景在于可以被类实现,从而实现各种复杂的设计模式,在TypeScript中,接口可以被类实现

在面向对象的编程方法学中,接口对于代码可维护性和业务逻辑解耦起着至关重要的作用

// ClockInterface 描述了一个属性和一个方法
interface ClockInterface {
  currentTime: Date;
  setTime(d: Date): void;
}

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

实现类必须包含接口所声明的全部必选属性,在上面的例子中:Clock类必须同时包含属性 currentTime 和方法 setTime

interface ClockInterface {
  currentTime: Date;
  setTime(d: Date): void;
}

// 错误,缺少属性 currentTime
// error TS2420: Class 'Clock' incorrectly implements interface 'ClockInterface'. 
// Property 'currentTime' is missing in type 'Clock'
class Clock implements ClockInterface {
  setTime(d: Date) {}
  constructor(h: number, m: number) { }
}

接口继承

接口也可以互相继承,通过继承,子接口将继承父接口的成员:

interface Shape {
    color: string;
}
interface Square extends Shape {
    sideLength: number;
}

// 正确,color 属性来自父接口
let square: Square = {
  color: 'blue',
  sideLength: 4
};
暂无评论