前言
本文收录于TypeScript知识总结系列文章,欢迎指正!
在介绍TS对象类型中,为了让数组每一项更具体,我们使用 string [ ] 表示字符串类型的数组,为了知道函数的参数与返回值,使用 let fn: (a: number, b: number) => number 来表示一个函数类型,那么作为复杂类型,仅仅使用Object表示一个普通对象类型是远远不能满足类型检查以及代码可读性的,有没有一种类型可以用来描述对象的结构呢?
这便是今天的主题:接口
定义
接口(Interface)是一种定义对象形状的方式,它指定了对象具备或拥有哪些属性和方法,可以用来定义对象属性值和属性名的类型。使用接口来定义对象可以使代码更健壮,清晰。与Java的接口不同,TS接口除了能够描述类,还可以描述对象,函数等。
用法
基本用法
接口使用interface作为关键词,与JS中类(class)的写法相似,下面是一个JS类
class Animal { color = "black"; showColor = () => this.color } console.log(new Animal().showColor());
我们定义了一个Animal类,其中包含1个属性以及1个行为;那么在我们抽象构想这个类时可能只知道它的类型,比如:color可能是字符串类型,showColor函数返回一个颜色字符串;
接口的写法如下
interface 接口名称 { 属性名: 属性类型 函数名(参数类型列表): 返回值类型 }
让我们稍作改动,用接口的方式实现这个类
interface Animal { color: string showColor: () => string }
怎么样?是不是觉得接口不算太难,只需要仿照class的写法,将类抽象成类的形状(属性的类型),就可以实现一个接口
参照之前基本类型的写法,我们新增一个对象,使用对象实现这个抽象的接口
interface Animal { color: string showColor: () => string } const animal: Animal = { color: "blue", showColor() { return this.color }, } console.log(animal.showColor());
约定规则
一般我们定义接口时,命名规则是在名称前加 I ,即上述接口名是:IAnimal
在使用接口定义对象时会遇到属性不匹配的情况,比如上述代码我们改成
const animal: Animal = { color: "black", name: "dog", showColor() { return this.color }, }
此时编辑器会提示 name 不在类型 Animal 中
那么我们去掉name和color,只保留showColor函数呢?
const animal: Animal = { showColor() { return this.color }, }
编辑器会提示:类型中缺少属性 color。
或者我们不想修改animal中的color属性,让它始终是black
那么有没有办法使接口支持上述属性的操作呢?请接着往下看
属性控制
在接口中,每个属性都有3种选项,分别是可选,只读,任意;换句话说,接口中的属性可以设置成可变的。
任意属性
在接口定义时,在属性名后面加上索引签名来表示接口可以有任意数量的属性。如:
interface Animal { color: string showColor: () => string [key: string]: unknown }
这个接口可以匹配约定规则中的第一段代码;
tips:索引签名参数(上述代码的key)类型支持number,string,symbol,模板字符;示例如下
interface Animal { [key: symbol | string | number]: unknown } const str = "name" const animal: Animal = { 0: "dog", [str]: "dog", [Symbol("name")]: "dog", }
可选属性
在定义接口时,我们可能无法判断某个属性是否存在。此时一个可选的属性操作可以为我们解决此问题,我们在属性名后面增加一个 ? 问号用于为属性增加可选操作,如:
interface Animal { color?: string } const animal: Animal = { color: "black" } const animal2: Animal = {}
此时color属性在对象中便可有可无,这个接口可以适配约定规则中的第二段代码。
只读属性
顾名思义,只读属性保证了对象中某个属性只允许读取,不允许修改
interface Animal { readonly color: string } const animal: Animal = { color: "black" } animal.color = "white"
上述代码中会在编译前报错:无法分配到 color ,因为它是只读属性
在JS中我们同样可以控制对象中属性的只读,即只设置属性get而不使用set操作,代码如下
const animal = { _color: "black", get color() { return this._color } } animal.color = "white"
定义函数
函数在接口中有两种表现形式,分别是冒号定义和箭头定义
冒号定义
interface 接口名 { 函数名 (参数类型) : 函数返回类型 }
箭头定义
interface 接口名 { 函数名: (参数类型) => 函数返回类型 }
接口类型
接口除了上述展示的对象接口外,还有函数接口,索引接口,继承接口,类接口,下面我会一一列举。
函数接口
在对象类型中,我们说到了函数类型的定义的方式有两种分别是Function关键词和 ( )=>void 箭头函数,那么在本文,我们会接触到第三种定义函数的方式,接口
interface IFn { (): void }
我们通过上述代码实现一个无返回值的函数接口,冒号(:)前面的括号表示参数,后面表示函数返回值,结合之前的知识,我们写一个加法函数
interface IFn { (a: number, b: number): number } const add: IFn = (a, b) => { return a + b }
索引接口
同样在对象类型文章中,我们提到了使用接口定义数组类型
interface IArray { [i: number]: any }
通过定义索引值 i 的类型为 number 来描述一个数组类型
interface IArray { [i: number]: string } const list: IArray = ['a', 'b', 'c']
继承接口
和JS中的类一样,接口类型也可以继承操作,被继承的接口拥有父接口的属性及方法
interface IAnimel { name: string } interface IDog extends IAnimel { likeMeat: boolean } interface IWhiteDog extends IDog { color: string } const whiteDog: IWhiteDog = { name: "阿黄", likeMeat: true, color: "white" }
上述代码实现了一个连续的接口继承,子类IWhiteDog拥有父类的属性。
继承接口与继承类不同,接口可以通过多继承实现,上述代码可以修改为以下代码
interface IAnimel { name: string } interface IDog { likeMeat: boolean } interface IWhiteDog extends IAnimel, IDog { color: string } const whiteDog: IWhiteDog = { name: "阿黄", likeMeat: true, color: "white" }
需要注意的是,执行多继承时,父接口的成员名可以重复,但类型必须相同
interface IAnimel { name: string likeMeat: string } interface IDog { likeMeat: boolean } interface IWhiteDog extends IAnimel, IDog { color: string }
上述代码会抛错:IAnimel 和 IDog 类型的命名属性 likeMeat 不完全相同
除了上面的继承接口外,TS还有一类继承,那便是接口继承类;TS与其他面向对象语言不同,它支持接口继承类中的属性类型及函数类型
将前面的代码修改一下,便可以达到和上面的代码一样的效果,实现接口对类的继承
class IAnimel { name = "阿黄" } class IDog { likeMeat = true } interface IWhiteDog extends IAnimel, IDog { color: string } const whiteDog: IWhiteDog = { name: "阿黄", likeMeat: true, color: "white" }
需要注意的是:接口继承的是类的接口(可以理解为声明类的同时会创建类实例的接口类型,这个接口类型被当做是类的接口),所以接口继承的类实际上是类实例的接口
我们使用以下代码可以证实上面的说法
class IDog { static _likeMeat = true likeMeat = false } interface IWhiteDog extends IDog { color: string } const whiteDog: IWhiteDog = { likeMeat: true, _likeMeat: true, color: "white" }
上述代码的抛错:
说明接口可以继承类实例的属性,却不可以继承类中的静态属性
类接口
接口是一种抽象的类型,它的作用是描述对象的形状,增强可读性和可维护性。在接口与类之间,TS提供了一个 实现 (implements)关键词区别于传统的冒号(:)赋予类型,下面是一个类接口的例子
interface IAnimel { name: string readonly color: string getColor: () => string getName?(): string } class Animal implements IAnimel { name = "dog" color = "black" getColor = () => this.color }
看到这里不知道你是否会有疑问:接口可以描述类中的属性,那是否可以对构造函数进行描述?
答案是可以,但是和常规写法稍有不同。我们在接口中使用new表示类中的constructor
interface IAnimel { name: string new(name: string): Animal }
然而上面接口的写法无法使用类来实现
interface IAnimel { name: string new(name: string): Animal } class Animal implements IAnimel { name: string; constructor(name) { this.name = name } }
为什么会抛错呢?
参考之前的一篇文章:JS继承,因为在TS的类型中,类的构造函数和实例是两个不同的类型:构造函数是一个特殊的函数,它在创建类的实例时被调用,并返回一个该类的实例;类的实例则包含了类的所有属性和方法
有没有使我们实现接口的同时对构造函数进行描述的方法呢?
且看下面的代码
interface IAnimel {// 描述实例 name: string } interface IAnimelConstructor {// 描述构造函数 new(name: string): Animal } class Animal implements IAnimel {// 实现接口 name: string; constructor(name) { this.name = name } } const createAnimal = (__Animal: IAnimelConstructor): IAnimel => {// 工厂模式解决接口的局限性 return new __Animal('dog') } const animal = createAnimal(Animal) console.log(animal);
代码中我使用两个接口来描述一个类的构造函数及实例,使用工厂模式解决接口的局限
总结
本文讲述了TypeScript中的接口类型,从定义,意义,用法,属性特性,继承,接口实现及局限性这几个方面详细的介绍了接口,通过代码案例了解其具体用法
感谢你看到了这里,如果文章对你有帮助,希望支持一下博主,谢谢。