详解TypeScript中的接口interface(1)

TS中的接口,即 interface ,既可以作为数据类型来使用,也可以当做其它语言中可以被类继承的接口来使用。下面我们先来介绍接口作为类型使用的特性。

typescript中的代码都可以在这里运行:

https://www.typescriptlang.org/play/index.html

定义和使用

TypeScript中,声明变量时需要指定变量的类型,基本类型有 string 、 number 等,但是对于包含特定属性的对象,基本类型是无法满足要求的。

比如,我们希望用一个对象来表示动物,这个对象需要含有下面三个属性:

  • name :表示动物的名字
  • food :表示喜欢吃的食物
  • weight : 表示大致体重

我们可以定义一个名为 Animal 的接口,然后指定它的属性的类型:

<code>interface Animal {
name: string;
food: string;
weight: number;
}/<code>

然后我们可以用Animal 来指定变量的类型:

<code>let monkey: Animal = { name: '猴子', food: '香蕉', weight: 100 }/<code>

如果赋值为一个对象,里面缺少某个属性,则会报错:

<code>let monkey: Animal = { name: '猴子', food: '香蕉' }/<code>
详解TypeScript中的接口interface(1)

可选属性

我们可以给Animal扩展一些类型,以应对不同的动物。比如,人作为一种动物,可以有国家的属性, 而猴子作为一种动物,没有国家的属性,但是可以有类别,比如金丝猴、狒狒等。对于某些动物有,另外一些动物没有的属性,我们可以定义为可选属性,即用一个 ? 来表示:

<code>interface Animal {
name: string;
food: string;
weight: number;
country?: string;
breed?: string;
}/<code>

这样,即使对象中没有可选属性,也不会报错:

<code>let person: Animal = { 
name: '人',
food: '啥都吃',
weight: 150,
country: 'China'
}
let monkey: Animal = {
name: '猴子',
food: '香蕉',
weight: 100,
breed: '金丝猴'
}/<code>


只读属性

可以将接口的某些属性指定为只读类型,这样就无法更改其中的属性了。比如我们可以把 name 指定为只读:

<code>interface Animal {
readonly name: string;
food: string;
weight: number;
country?: string;
breed?: string;
}/<code>

这时再去改变属性值会报错:

详解TypeScript中的接口interface(1)


根据属性名定义属性类型

世界上的动物有很多种,必须用很多个属性来形容动物,所以 Animal 应该还可以有其它很多种属性,比如性别啊、栖息环境啊等等,但是我们不可能一一列举出所有属性,一是因为代码太多了,二是我们也不知道到底有多少种属性,所以我们希望能用一个比较抽象的方式来表示 Animal 接口中还有其它属性。

TypeScript提供了这种功能,让我们指定任意属性值:

<code>interface Animal {
name: string;
food: string;
weight: number;
country?: string;
breed?: string;
[propName:string]: any;
}/<code>

我们使用了 [propName:string]: any ,来表明在Animal类型的数据中,对象可以包含任何字符串类型的key,并且对应key的值可以是任何类型。

有了上述声明, Animal 类型的对象就可以添加任何属性了,只要该属性是字符串类型的。我们可以称这种声明为

签名式属性声明


使用限制

签名式属性声明有2个限制,其一是接口中其它显式声明的属性的类型必须是签名式声明的类型的子集。也就是说,如果有了如下声明: [propName:string]:number ,那么接口中其它 key 为 string 类型的属性,取值都必须是 number 类型。

例如下面的代码报错:

详解TypeScript中的接口interface(1)

因为 weight 很明显是一个 string 类型的key,但是它的取值类型为 number ,这就和 [propName:string]:string 冲突了。必须改用下面的写法:

<code>interface Animal {
name: string;
food: string;
weight: number;
[propName: string]: string | number;
}/<code>

string | number 是一种联合类型,表明该类型可以是 string 或 number , number 当然是其子集。


另一个限制签名式属性声明可选属性会发生冲突。例如下面的代码:

<code>interface Animal {
name: string;
food: string;
weight: number;
country?: string;
[propName: string]: string | number;
}
// 会报错,提示country 取值为undefined | string, 和 string | number 不兼容/<code>

因为已经声明了 country 是可选属性,也就是说 country 的属性值是 undefined | string

类型,这种类型显然是和 string | number 是不兼容的。


解法有两个:

  • 去掉可选属性 country 。
  • [propName: string] 增加一种取值类型,即 string | number | undefined
<code>interface Animal {
name: string;
food: string;
weight: number;
country?: string;
[propName: string]: string | number | undefined;
}/<code>


多余类型检查

默认情况下,如果给某个类型的值传了多余的属性,TS编译器会报错,比如多传了一个 ability 属性:

<code>let monkey: Animal = { 
name: '猴子',
food: '香蕉',
weight: 100,
breed: '金丝猴',

ability: '爬树',
}/<code>

报错提示 Animal 不存在 ability 属性:

详解TypeScript中的接口interface(1)

所以我们尽量应该按照接口定义的数据格式去赋值,不然会报错。


有3种方法可以避免这种报错,下面分别来讲解。

1. 类型断言

我们可以用 as 操作符,明确告诉TS,我们给出的某个值就是另一种类型,不必再报错了,如下:

<code>interface Animal {
name: string;
food: string;
weight: number;
country?: string;
[propName: string]: string | number | undefined;
}
let monkey2: Animal = {
name: '猴子',
food: '香蕉',
weight: 100,
breed: '金丝猴',
ability: '爬树'
} /<code>

再举个栗子:

<code>function consoleAnimal(a: Animal) {
console.log(a.name);
}
consoleAnimal({ name: '猴子', food: '香蕉', weight: 100, ability: '爬树' });/<code>

这时候会报错,告诉你传给函数的参数不符合 Animal 接口的规定,但是我们可以用 as 操作符来将参数指定为 Animal 类型, 如下,就不会再报错了:

<code>function consoleAnimal(a: Animal) {
console.log(a.name);
}
const aMonkey2 = { name: '猴子', food: '香蕉', weight: 100, ability: '爬树' };
consoleAnimal(aMonkey2);/<code>

如前文所述,给 Animal 增加 字符串类型的属性值,并指定取值类型为 any 既可任意添加属性:

<code>interface Animal {
name: string;
food: string;
weight: number;
country?: string;
[propName: string]: string | number | undefined;
}
let monkey2: Animal = {
name: '猴子',
food: '香蕉',
weight: 100,
breed: '金丝猴',
ability: '爬树'
} /<code>

3. 通过中间变量传递值能跳过TS的检查

我们先将值赋值给一个中间变量,再把中间变量赋值给 我们想要用的变量,既可跳过TS的检查。如下例子:

<code>interface Animal {
name: string;
food: string;
weight: number;
country?: string;
}
const aMonkey = {
name: '猴子',
food: '香蕉',
weight: 100,
breed: '金丝猴',
ability: '爬树'
};
let monkey2: Animal = aMonkey;/<code>

函数传值也可以使用该方法:

<code>function consoleAnimal(a: Animal) {
console.log(a.name);

}
const aMonkey2 = { name: '猴子', food: '香蕉', weight: 100, ability: '爬树' };
consoleAnimal(aMonkey2);/<code>


使用接口规定函数类型

我们也可以使用接口来定义一个函数的格式,语法如下:

<code>interface FuncName {
(parameter1: string, parameter2: number): boolean;
}/<code>

即定义函数的参数列表参数类型返回值类型,可以看出,上述代码有两个参数,分别为 string 类型和 number 类型,返回值为 boolean 类型。

如果指定某个函数的类型为该接口类型,该函数的定义就必须符合条件:

<code>let mySearch: FuncName;
mySearch = function(para1: string, para2: number): boolean {
return para1.slice(para2).length > 4;
};/<code>

从上面的代码可以看出,定义函数时的参数,不必一定要和接口定义中的参数名相同,只要位置相对应即可。

因为我们已经指定了变量为 FuncName 类型,所以如果在定义参数时,不写参数类型和返回值类型,TS也能自动推断出对应的类型。

<code>let mySearch: FuncName;
mySearch = function(para1, para2) {
return para1.slice(para2).length > 4;
};/<code>


分享到:


相關文章: