TS-接口

650 阅读2分钟

介绍

接口的作用就是为了这些类型命名和个人代码或第三方代码定义契约。在面向对象的编程中,接口是一种规范的定义,它定义了行为和动作的规范,在程序设计里面,接口起到一种限制和规范的作用。接口定义了某一批类所需要遵守的规范,接口不关心这些类的内部状态数据,也不关心这些类立方法的实现细节,它只规范这批类里面必须提供某些方法,提供这些方法的类就可以满足实际需要,typescript 中的接口类似 Java ,同时还增加类更灵活的接口类型,包括属性、函数、可索引和类等。

初始

  • 最初的方法参数规范
function add(num: number): number {
    return 1 + num;
}
// console.log(add('2')); // error 类型“"2"”的参数不能赋给类型“number”的参数。
console.log(add(2));
  • 可以使用对象的形式,去规范很多的参数
function label(labelObj: {label: string}): void {
    console.log('labelObj.label');
}
// label(12); // error 类型“12”的参数不能赋给类型“{ label: string; }”的参数。
// label('12'); // error 类型“"12"”的参数不能赋给类型“{ label: string; }”的参数。
// label({name: '12'}); // error 类型“{ name: string; }”的参数不能赋给类型“{ label: string; }”的参数。
                     // error 对象文字可以只指定已知属性,并且“name”不在类型“{ label: string; }”中。
label({label: '12'}); // 正常
  • 使用接口的方式规范,对象约束
interface People {
  name: string;
  age: number;
  isBoy: boolean;
}

function printPeple(propleObj: People) {
  console.log(`${propleObj.name} 同学,现在${propleObj.age}岁了,是位很${propleObj.isBoy ? '帅气的' : '美丽的'}人儿`);
}
let obj = { 
	name: '六六', 
    age: 24, 
    isBoy: true 
};
printPeple(obj); // 六六 同学,现在24岁了,是位很帅气的人儿

接口好比一个名字,用来描述上面例子里的要求。我们只会关注值的外形,只要传入的对象满足上面的提到的必要条件,那么就是被允许的。
注:

  • 类型检查器不会去检查属性的顺序。
  • 只要相应的属性存在并且类型也是对的即可。

可选属性

接口中的属性并不是全部需要的,即给函数传入的参数对象中只有部分属性赋值。

// People接口
interface People {
  name: string;
  age: number;
  isBoy?: boolean; // 可选属性
  height?: number; // 可选属性
  color?: string; // 可选属性
}

function createPeople(peopleObj: People): { height: number; color: string } {
  let newPeople = { name: '', age: 0, height: 188.8, color: '红色' };
  if (peopleObj.color) {
    newPeople.color = peopleObj.color;
  }
  if (peopleObj.height) {
    newPeople.height = peopleObj.height;
  }
  newPeople.name = peopleObj.name;
  newPeople.age = peopleObj.age;
  return newPeople;
}
let myPeople = createPeople({ name: '苏苏', age: 23, color: '白色' });
let { height, color } = myPeople;
console.log(myPeople); // {name: "苏苏", age: 23, height: 188.8, color: "白色"}
console.log(height); // 188.8
console.log(color); // 白色

注:可选属性的接口与普通定义的接口定义差不多,只需要在可选属性名字后面添加 ? 符号。
可选属性的好处:

  • 可以对可能存在的属性进行预定义。
  • 可以捕获引用了不存在的属性时的错误。

只读属性

对象属性只能在刚刚创建时修改其值,可以在属性名前面用 readonly 来指定只读属性。

// 动物接口
interface Animal {
  name: string;
  readonly age: number;
  heigth?: number;
}

let animal: Animal = { name: '大象', age: 18, heigth: 2.22 };
// animal.age = 20; //error: Cannot assign to 'age' because it is a read-only property.
console.log(animal); // {name: "大象", age: 18, heigth: 2.22}

ReadonlyArray<T> 类型与 Array<T> 类型相似,只是吧所有可变方法都去掉了,因此可确保数组创后不能被修改。

let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // error!
ro.push(5); // error!
ro.length = 100; // error!
a = ro; // error!

从上面代码的最后一行可以看出,就算把整个 ReadonlyArray 重新赋值到一个普通数组也是不可以的,但是可以用类型断言重写。

a = ro as number[];

readonly VS const

最简单判断该用 readonly 还是 const 的方法是看要把它作为一个变量使用还是一个属性使用。作为变量使用的话选择 const,作为属性则使用 readonly

额外的属性检查

当将它们赋值给变量或作为参数传递的时候,当将它们赋值给变量或作为参数传递的时候。
绕开这些检查最简单的方式:

  • 使用 类型断言
  • 可以添加一个字符串索引签名
  • 将这个对象赋值给另一个变量,因为不会经过额外属性检查,编译器不会报错。
  • 使用 [propName: string]: any; 是指任意数量的其他属性
interface People {
  name: string;
  age: number;
  isBoy?: boolean;
  height?: number;
  color?: string;
  [propName: string]: any;
}

let peopleObj = { name: '苏猫咪', age: 18, sex: '女生' };
let suPeo = createPeople(peopleObj);
console.log(suPeo);

函数类型

接口能够描述 JavaScript 中队形拥有的各种各样的外形。除了描述带有属性的普通对象外,接口也可以描述函数类型。
为了使用接口表示函数类型,我们需要给接口定义一个调用签名。他就像一个只有参数列表返回值类型的函数定义。

// 加密的函数类型接口
interface encrypt {
    (key: string, value: string): string;
}
let md5: encrypt = function(key: string, value: string): string {
    return key + value;
}
console.log(md5('name','李四'));

可索引接口

数组、对象的约束(不常用)

  • 可索引接口-----对数组的约束
// 可索引接口, 数据、对象的约束(不常用)
interface UserArr {
    [index: number]: string; // index是索引 是number类型   value是string类型
}
// let arr: UserArr = [12,12,2]; // error 不能将类型“number”分配给类型“string”。
let arr: UserArr = ['12','4'];
console.log(arr); // ["12", "4"]
  • 可索引接口-----对对象的约束
interface UserObj {
    [inde: string]: string; // index 是 string类型    value 是 string 类型
}
// let userObj: UserObj = ['张三', '李四']; // error 不能将类型“string[]”分配给类型“UserObj”。类型“string[]”中缺少索引签名。
let userObj: UserObj = {'name': '张三'};
console.log(userObj); // {name: "张三"}

类类型接口

对类的约束。和抽象类有点类似。

interface Animal {
    name: string;
    eat(foodName: string): string;
}
  • 可以不传 接口中的方法的参数
// 类类型接口: 对类的约束      和抽象类有点类似
class Cat implements Animal {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
    eat(): string {
        return `${this.name}在吃食物`;
    }
}
let cat = new Cat('小猫咪');
console.log(cat.eat()); // 小猫咪在吃食物
  • 可以传 接口中的方法的参数
class Dog implements Animal {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
    // 必须与Animal接口中的方法与属性一致,并将方法实现
    eat(foodName: string): string {
        return `${this.name}在吃${foodName}`;
    }
}
let dog = new Dog('花花');
console.log(dog.eat('狗粮')); // 花花在吃狗粮

接口的继承

接口可以继承,

interface Animal {
    eat(): void;
}
interface Person extends Animal {
    work(): void;
}
class Boy implements Person {
    constructor(name: string) {
        super(name);
    }
    work(): void {
        console.log(this.name + '工作是写代码');
    }
    eat(): void {
        console.log(this.name + '喜欢吃苹果');
    }
}
let boy = new Boy('张三');
boy.eat(); // 张三喜欢吃苹果
boy.work(); // 张三工作是写代码

可以即继承又实现

interface Animal {
    eat(): void;
}
interface Person extends Animal {
    work(): void;
}
class Programmer {
    public name: string;
    constructor(name: string) {
        this.name = name;
    }
    coding(code: string): void {
        console.log(this.name + code);
    }
}
class Boy extends Programmer implements Person {
    constructor(name: string) {
        super(name);
    }
    work(): void {
        console.log(this.name + '工作是写代码');
    }
    eat(): void {
        console.log(this.name + '喜欢吃苹果');
    }
}
let boy = new Boy('张三');
boy.eat(); // 张三喜欢吃苹果
boy.work(); // 张三工作是写代码
boy.coding('写ts'); // 张三写ts