上一节学习了 TypeScript 的基本类型,本节再来学习下接口 Interfaces 的使用。
TypeScript 的一个重要的特性就是帮助检查参数的字段是否是合法的,比如检查某个参数包含字段 foo,且类型需要是 number 类型,否则就会报错。通过接口,即 Interface 我们可以方便地实现这个操作。
第一个 Interface
最简单的实现方式参考下面的例子:
1 |
function printLabel(labeledObj: { label: string }) { |
在这里我们声明了一个方法,叫做 printLabel,它接收一个参数叫做 labeledObj,在 labeledObj 后面声明了该参数需要的字段和类型,这里它需要一个字段 label,且类型必须要是 string 类型。在调用时,我们传入了一个 Object,它包含了两个字段,一个是 size,类型为 number,另一个是 label,类型为 string。这里值得注意的是传入的参数是比声明的参数多了一个字段 size 的,但是这并没有关系,编译器只检查传入的参数是否至少存在所需要的属性,对于多余的属性是不关心的。
运行结果如下:
1 |
[LOG]: "Size 10 Object" |
如果此时我们将 label 属性的类型修改为 number:
1 |
function printLabel(labeledObj: { label: number }) { |
则会出现如下报错:
1 |
Argument of type '{ size: number; label: string; }' is not assignable to parameter of type '{ label: number; }'. Types of property 'label' are incompatible. Type 'string' is not assignable to type 'number'. |
这里就提示 label 属性只能传入 number 类型,而不能是 string 类型。
但上面这个写法其实很不友好,如果属性比较多,那这个声明会非常复杂,而且不同方法如果都用到这个参数,难道还把它的声明都重复声明一遍?这也太不好了吧。
所以,为了更方便地实现声明,这里我们可以使用 Interface 来实现,上面的例子就可以改写为如下形式:
1 |
interface LabeledValue { |
这里我们使用 interface 声明了一个类型声明,这样在 printLabel 就可以直接使用 Interface 的名称了。
怎么样?这种写法是不是感觉好多了。
Optional properties
某些情况下,某些字段并不是完全必要的,比如看下面的例子:
1 |
interface LabeledValue { |
其中 message 字段其实在 myObj 对象里面没有,而且这个字段也并不是必需的,但是该字段如果存在的话,必须是 string 类型。那像上面的写法,其实就会报错了:
1 |
Argument of type '{ size: number; count: number; label: string; }' is not assignable to parameter of type 'LabeledValue'. Property 'message' is missing in type '{ size: number; count: number; label: string; }' but required in type 'LabeledValue'. |
这里说 message 字段没有传。
这时候我们可以将 message 标识为可选字段,只需要在字段后面加个 ?
就好了,写法如下:
1 |
interface LabeledValue { |
这样就不会再报错了。
Readonly properties
在某些情况下,我们期望一个 Object 的某些字段不能后续被修改,只能在创建的时候声明,这个怎么做到呢?很简单,将其设置为只读字段就好了,示例如下:
1 |
interface Point { |
这里我们声明了一个名为 Point 的 Interface,然后在创建 Point 的时候将 x 设置为 10。但后续如果我们想设置 x 的属性为 5 的时候,就会报错了:
1 |
Cannot assign to 'x' because it is a read-only property. |
这样就可以保证某些字段不能在后续操作流程中被修改,保证了安全性。
另外我们可以使用 ReadonlyArray 来声明不可变的 Array,一旦初始化完成之后,后续所有关于 Array 的操作都会报错,示例如下:
1 |
let a: number[] = [1, 2, 3, 4]; |
另外到这里大家可能疑惑 readonly 和 const 是什么区别,二者不都代表不可修改吗?其实区分很简单,readonly 是用来修改 Object 的某个属性的,而 const 是用来修饰某个变量的。
Function Types
除了用 interface 声明 Object 的字段,我们还可以声明方法的一些规范,示例如下:
1 |
interface SearchFunc { |
这里就是用 interface 声明了一个 Function,前半部分是接收的参数类型,后面 boolean 是返回值类型。
声明 interface 之后,我们便可以声明一个 Function 了,写法如下:
1 |
interface SearchFunc { |
这里声明了一个 Function 叫做 mySearch,其中其参数和返回值严格按照 SearchFunc 这个 Interface 来实现,那就没问题。
如果我们将返回值改掉,改成非 boolean 类型,示例如下:
1 |
interface SearchFunc { |
这时候就会得到如下报错:
1 |
Type '(source: string, subString: string) => number' is not assignable to type 'SearchFunc'. Type 'number' is not assignable to type 'boolean'. |
这里就说返回值是 number,而不是 boolean。
Class Types
除了声明 Function,interface 还可以用来声明 Class,主要作用就是声明 class 里面所必须的属性和方法,示例如下:
1 |
interface ClockInterface { |
这个简直跟其他语言的接口定义太像了。定义好了 ClockInterface 之后,class 需要使用 implements 来实现这个接口,同时必须要声明 currentTime 这个变量和 setTime 方法,类型也需要完全一致,不然就会报错。
Extending Interfaces
另外 Interface 之间也是可以继承的,相当于在一个 Interface 上进行扩展,示例如下:
1 |
interface Shape { |
这里 Shape 这个 Interface 只有 color 这个属性,而 Square 则继承了 Shape,并且加了 sideLength 属性,那其实现在 Square 这个接口声明了 color 和 sideLength 这两个属性。
另外 Interface 还支持多继承,获取被继承的 Interface 的所有声明,示例如下:
1 |
interface Shape { |
但如果同名的字段不一致怎么办呢?比如下面的例子:
1 |
interface Fruit { |
这里 hasLeaf 在 Apple 里面是 boolean 类型,在 Orange 里面是 number 类型,最后 Watermalon 继承了这两个 Interface 会怎样呢?
很明显,报错了,结果如下:
1 |
Interface 'Watermalon' cannot simultaneously extend types 'Apple' and 'Orange'. Named property 'hasLeaf' of types 'Apple' and 'Orange' are not identical. |
意思就是说字段类型不一致。
所以,要多继承的话,需要被继承的 Interface 里面的属性不互相冲突,不然是无法同时继承的。
Interfaces Extending Classes
在某些情况下,Interface 可能需要继承 Class,Interface 扩展 Class 时,它将继承该 Class 的成员,但不继承其实现。这就类似该 Interface 声明了该类的所有成员而没有提供实现。
另外 Interface 甚至可以继承 Class 的私有成员和受保护成员。这意味着,当创建一个扩展带有私有或受保护成员的 Class 的 Interface 时,该 Interface 只能由该 Class 或其子 Class 实现。
示例如下:
1 |
class Control { |
上面我们可以知道,当创建一个扩展带有私有或受保护成员的 Class 的 Interface 时,该 Interface 只能由该 Class 或其子 Class 实现。在这里 ImageControl 由于没有继承 Control,但同时 Control 还包含了私有成员变量,所以 ImageControl 并不能继承得到 state 这个私有成员变量,所以会报错。
以上便是关于 Interface 的一些用法,后面会继续总结其他的用法,如 Functions、Classes 等详细用法。