Skip to content

接口

什么是接口 interface

https://www.tslang.cn/docs/handbook/interfaces.html

接口也是一种类型, 可以约束对象或者类的属性和方法, 只能有哪些属性或者必须有哪些属性

typescript
// 定义一个 PersonInterface 的接口, 被这个接口约束的变量,必须要有 name 属性和 age 属性,
// 并且, 这两个属性的类型必须要和接口中定义的一致
interface PersonInterface {
  name: string;
  age: number;
}

const p1 = {
  name: "tom",
  age: 10,
};

const p2 = {
  name: "tom",
  age: 10,
  gender: "man", // 报错, 比接口多一个 gender 属性
};

const p3 = {
  name: "tom", // 报错, 比接口少一个 age 属性
};

可选属性

默认情况下, 被接口限制的变量, 属性既不能多, 也不能少, 但是如果有可选属性就能让约束的变量只实现一部分属性

typescript
interface FullNameInterface {
  firstName: string;
  lastName: string;
  otherName?: string;
}

const n1: FullNameInterface = {
  firstName: "zhang",
  lastName: "shang",
};

索引签名

默认情况下, 被接口限制的变量, 属性既不能多, 也不能少, 但是如果用索引签名 就能让约束的变量多一部分自己独有的属性

typescript
interface FullName {
  firstName: string;
  lastName: string;
  [key: string]: any; // 任意的 key 并且不限制 value 的类型
  // [propName]: any; 老版本 ts 的写法是这样的
}

const names = {
  firstName: "wang",
  lastName: "Hai",
  otherName: "XiaoWang", // 如果没有索引签名, 添加这个属性就会报错
};

只读属性

默认情况, 被约束的变量只要实现了接口属性就能随意读取和修改, 但是有些属性可能只需要 在定义的时候赋值一次 然后就不允许修改了, 此时就需要使用只读属性

typescript
interface FullNameInterface {
  readonly firstName: string;
  lastName: string;
}

let p1: FullNameInterface = {
  firstName: "zhang",
  lastName: "shang",
};

p1.firstName = "li"; // 报错: firstName 不能被修改
p1.lastName = "si";

函数接口

用来约束函数的参数个数类型及函数的返回值

typescript
// 实现这个接口的函数, 必须有2个参数, 并且类型必须是 number, 返回值的类型也必须是 number
interface sumInterface {
  (x: number, y: number): number;
}

const add: sumInterface = function (x, y) {
  return x + y;
};

嵌套结构

typescript
interface RouteConfig {
  name: string;
  path: string;
  meta?: object;
  children?: Array<RouteConfig>; // 引用自己, 必须是可选的, 否则死循环
}

// 使用 type 关键字也是可以的
// type RouteConfig = {
//   name: string;
//   path: string;
//   meta?: object;
//   children?: Array<RouteConfig>;
// }

const routes: RouteConfig[] = [
  {
    name: "login",
    path: "/login",
    meta: { noLogin: true },
  },
  {
    name: "home",
    path: "/",
    children: [
      {
        name: "user_list",
        path: "/users",
      },
    ],
  },
];

混合类型接口

https://www.tslang.cn/docs/handbook/interfaces.html

接口继承

  • 接口继承是可以继承多个的
typescript
interface LengthInterface {
  length: number;
}

interface WidthInterface {
  width: number;
}

interface HeightInterface {
  height: number;
}

interface RectInterface
  extends LengthInterface,
    WidthInterface,
    HeightInterface {
  color: string;
}

// 必须四个属性都有, 否则就报错
const rect: RectInterface = {
  length: 10,
  width: 20,
  height: 30,
  color: "red",
};

接口继承类

typescript
class Person {
  public name;
  public constructor(name: string) {
    this.name = name;
  }
  public eat(): void {
    console.info("人需要吃饭");
  }
}

// PersonInterface接口会继承Person类的所有属性和方法
// 注意: 虽然会继承方法, 但是不会继承方法的实现
interface PersonInterface extends Person {
  age: number;
  say(): void;
}

class Student implements PersonInterface {
  name: string;
  age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  eat() {
    console.info("学生在学校吃饭");
  }

  say() {
    console.info(`我的名字是:${this.name}, 我的年龄是:${this.age}`);
  }
}

const s1 = new Student("张三", 15);
s1.say(); // 我的名字是:张三, 我的年龄是:15
s1.eat(); // 学生在学校吃饭

接口合并

定义两个同名的接口, 会自动合并, 如果去实现这个接口, 需要实现所有的属性和方法

typescript
interface PersonInterface {
  gender: boolean;
}

interface PersonInterface {
  age: number;
}

// 必须同时实现所有的定义
class Student implements PersonInterface {
  gender: boolean = true;
  age: number = 18;
}

关于接口的命名规范问题

  1. 以 I 为前缀: ICreateUserArgs
  2. 以 Interface/contract 为后缀: CreateUserArgsInterface CreateUserArgsContract

类 class

修饰符

  • public: 不设置权限,任意位置都可以访问,如果没有修饰符,默认 public
  • protected: 本类及其继承本类的派生类可以访问
  • private: 仅在本类内部可以访问, 在继承本类的派生类中不可覆盖
  • readonly: 只读属性不允许修改(可以与其他 4 个权限修饰符共用)
  • static: 静态属性, 这个属性属于类, 而不是对象(可以与其他 4 个权限修饰符共用)
typescript
class User {
  private username: string;
  private email: string;
  private password: string;
  constructor(username, email, password) {
    this.username = username;
    this.email = email;
    this.password = password;
  }
  public getUsername() {
    return this.username;
  }
  protected getEmail() {
    return this.email;
  }

  // 只能在本类内部访问, 否则报错
  private getPassword() {
    return this.password;
  }
}

class VIPUser extends User {
  readonly isVIP: boolean = true;
  static VIPLevels: Array<string> = ["会员", "超级会员", "永久会员"];
  constructor(username, email, password) {
    super(username, email, password);
  }
  public getUsername() {
    // 在子类内部访问 public 修饰的方法
    return "VIP " + this.getUsername();
  }
  public getEmail() {
    // 在子类内部访问 protected 修饰的方法
    return `<span style="color: #f00">${this.getEmail()}</span>`;
  }
}

const tom = new User("tom", "tom@qq.com", "111111");
console.log(tom.getUsername());
// 在外部访问 public 修饰的方法

const jerry = new VIPUser("jerry", "jerry@qq.com", "222222");
// 在子类的外部访问 public 修饰的方法

jerry.isVIP = false; // 报错

console.log(VIPUser.VIPLevels);

继承 & 抽象类 & 实现接口

  1. 静态属性, 继承与 js ES6 中语法是一样的, 但是在 js 中默认是没有抽象类, 接口这些概念的, 也没有 interfaceimplementsabstract 关键字
  2. js 中默认没有抽象类, 可以简单理解为, 抽象类就是一个特殊的接口
  3. 实现接口: interface 就是制定标准的关键字, implements 就是实现标准的关键字
typescript
interface IUser {
  id: number;
  username: string;
  email: string;
  password: string;
  gender: number;
}

// 抽象类可以定义方法不实现, 也可以直接实现
// 不能直接用 new 关键字来创建抽象类的实例
abstract class Animal {
  public eat(food: string): void {
    console.log("动物需要吃食物");
  }
  public sleep(time: number): void;
}

// 子类如果继承抽象类, 必须要实现父类的方法
// 在 typescript 中, 实现 interface 可以使用多个
class User extends Animal implements IUser {
  eat(food: string): void {}
  sleep(time: number): void {}
}

使用静态属性实现单例

typescript
class Util {
  private static instance: Util | null = null;
  private consturctor() {
    // 1. 使用 private 修饰 constructor
    // 让这个类不能直接使用 new 来创建对象
  }
  static getInstance() {
    // 2. 定义一个静态属性, 用于保存实例,
    // 如果这个实例不为 null, 那就证明已经实例化过一次了,
    // 直接返回这个实例
    if (Util.instance === null) {
      Util.instance = new Util();
    }
    return Util.instance;
  }
}

const util1 = Util.getInstance();
const util2 = Util.getInstance();
console.log(util1 === util2); // true

type 和 interface 的区别

相同点

  1. 都可以用来约束(描述)对象和函数(参数, 返回值)
  2. 都可以扩展(interface:可以继承, type: 可以使用交叉类型)
typescript
// 可以描述对象或者方法
type CreateUserArgs = {
  telphone_number: string;
  email: string;
  password: string;
  gender: number;
};

type updateUser = (id: number, user: CreateUserArgs) => void;

interface ICreateUserArgs {
  telphone_number: string;
  email: string;
  password: string;
  gender: number;
}
interface IUpdateUser {
  (id: number, user: ICreateUserArgs): void;
}

// 可以扩展
type UpdateUserArgs = CreateUserArgs & { id: number };

interface IUpdateUserArgs extends ICreateUserArgs {
  id: number;
}

不同点

  1. interface 可以但是 type 做不到的
typescript
// 重名的接口自动合并
interface IUser {
  id: number;
  email: string;
}
interface IUser {
  password: string;
  gender?: number;
}

// 最终合并的接口为
interface IUser {
  id: number;
  email: string;
  password: string;
  gender?: number;
}
  1. type 可以但是 interface 做不到的
typescript
// 1. 描述非引用类型数据
type numstr = number | string;
let str = 11;
str = "hello";

// 2. 联合类型
type empty = null | undefined;

Released under the MIT License.