接口概念
我觉得可以把接口看成是个面向对象类型变量的定义规范。
比方定义一个对象的规范,用关键词interface
去定义:
interface Person {
name: string;
age: number;
// 函数写法
// fn?: (number) => number;
// add: (a: number, b: number) => number;
}
let p: Person = { // 使用了Person的定义规范,就必须遵循,属性多了少了都会报错
name: 'p',
age: 25
};
注意:编译成es5后,接口的定义会被去掉!
对象接口定义
可选属性
用?
符号去定义接口内的属性,可以变成一个可选属性:
interface Person {
name: string;
age?: number;
}
let p: Person = { // 这样就不一定要去设置那个可选属性
name: 'p'
};
任意属性(索引签名)
接口开放个自定义属性:
interface Person {
name: string;
age?: number;
[A: string]: any; // 自定义属性----key为string类型,val为任意类型
// 或者[A: string]: string | number;
readonly [B: number]: string ; // key为number类型,val为字符串类型
}
let p: Person = {
name: 'p',
gender: 'male',
1: '1'
};
注意:
- 自定义key的属性中value值的类型一定要是其他同类型key属性中value值的父集。比如上面的
name: string
和age?: number
中,自定义属性为string
的val类型any
就是包含了string
和number
。而自定义属性为number
的val类型string
,因为没有其他key定义数字类型,所以就不受限制。 - key只能为
string
或者number
类型。
还有一种写法,不在接口中做文章:
interface Person {
name: string;
age: number;
}
let obj : {[key: string]: Person } = {
1 : {name: 'p', gender: 'male'},
2 : {name: 'p', gender: 'male'},
}
当然,自定义属性也是有缺点的,ts不允许只同时声明不同类型的自定义属性:
interface HA{
[rank: number]: string; // ts(2413) 数字索引类型 string 类型不能赋值给字符串索引类型 number
[prop: string]: number;
}
只读属性
就理解成变量申明的时候就定下来,之后不可再次修改的属性:
interface Person {
readonly id: number;
readonly age: number;
name: string;
}
let tom: Person = {
id: 1,
name: 'p',
};
tom.id = 2; // 会报错
tom.age = 18 // 会报错,就算你变量申明的时候没有赋值也不给修改了
函数接口的定义
整体定义
interface Fn {
(x: number, y: number): number // 定义了入参和返回值类型
}
let fn: Fn
fn = function(x: number, y: number): number {
return x+y
}
入参定义
interface X{
name: string;
}
function fn(x: X) {
return x.name;
}
注意:如果是不完全遵循接口的变量做入参可以比接口定义的属性多,如果是直接在函数形参上直接写的话,就必须严格遵循接口定义。都不推荐这么搞。
interface X{
name: string;
}
let str = {name: 'xiaoming', age: 12}
function fn(x: X) {
return x.name;
}
fn(str) // 不会报错
fn({name: 'xiaoming', age: 12}) // 报错
上面的现象(变量做入参可以比接口定义的属性多)是因为TS采用的是鸭子类型,像java的话就比较严格,不能这么搞。
实际开发中还是会以类型别名的方式去规范函数,因为方便。
数组接口的定义
interface Arr {
[index: number]: number // 意思就是下标为number类型,子项也为number类型
}
let arr: Arr = [1, 2, 3]
数组接口也叫可索引接口,如果把索引改成非数字,就变成对象属性为索引的接口了,一般不会这么写:
interface Obj{
[index: string]: string
}
let obj: Obj = {name: 'wow'} // 不推荐这样写
类接口的定义
用关键词implements
来指定接口:
interface Alarm {
name: string;
alert(): void;
}
interface Light {
lightOn(): void;
lightOff(): void;
}
class Car implements Alarm, Light { // 类的声明按照接口定义
name: string;
constructor(name: string){
this.name = name
}
alert() {
console.log('Car alert');
}
lightOn() {
console.log('Car light on');
}
lightOff() {
console.log('Car light off');
}
}
// 当既有继承又有接口时
class Plane {}
class SmallPlane extends Plane implements Alarm { // 应该是从左向右看的,SmallPlane 继承Plane ,并且要符合Alarm 接口
name: string;
constructor(name: string){
super()
this.name = name
}
alert() {
console.log('xxxx')
}
}
接口与接口的继承
interface A {
name: string;
fn: (number) => number
}
interface B extends A{
name: string // name:number 会报错
}
// 此时B也有了A的内容,并且B的name覆盖了A的,当然他们之间的name不能定义为不同的类型,会报错
接口继承类
class Point {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
interface Point3d extends Point {
z: number;
}
let point3d: Point3d = {x: 1, y: 2, z: 3};
具体为什么在TS中接口能够继承类看这个文章:接口继承类。不过应该很少会用到。
泛型接口的定义
看我这篇文章,了解泛型,然后看给接口设置泛型这个标题。