Skip to content

TypeScript 接口

在 TypeScript 中,接口(Interface)是一种类型定义,用于描述对象的结构和类型。接口可以定义对象的属性、方法、索引签名等,它只关注对象的“形状”,而不关注具体的实现。本文将详细介绍 TypeScript 中的接口。

1. 基本用法

声明接口

接口的声明使用 interface 关键字。

typescript
interface Person {
  name: string;
  age: number;
}

// 使用接口
let person: Person = {
  name: "John",
  age: 30,
};

// 错误:缺少属性
// let person: Person = { name: "John" }; // 缺少属性 "age"

// 错误:多余属性
// let person: Person = { name: "John", age: 30, gender: "male" }; // 对象字面量只能指定已知属性

接口的使用

接口可以用于类型注解,确保对象的结构符合预期。

typescript
interface Person {
  name: string;
  age: number;
}

// 函数参数类型
function greet(person: Person): string {
  return `Hello, ${person.name}! You are ${person.age} years old.`;
}

// 函数返回值类型
function createPerson(name: string, age: number): Person {
  return { name, age };
}

// 变量类型
let person: Person = { name: "John", age: 30 };
console.log(greet(person)); // 输出:Hello, John! You are 30 years old.

let newPerson = createPerson("Jane", 25);
console.log(greet(newPerson)); // 输出:Hello, Jane! You are 25 years old.

2. 接口的属性

可选属性

接口的属性可以是可选的,使用 ? 标记。

typescript
interface Person {
  name: string;
  age: number;
  gender?: string; // 可选属性
  email?: string; // 可选属性
}

// 正确:所有必选属性都有值
let person1: Person = { name: "John", age: 30 };

// 正确:包含可选属性
let person2: Person = { name: "John", age: 30, gender: "male" };

// 正确:包含多个可选属性
let person3: Person = {
  name: "John",
  age: 30,
  gender: "male",
  email: "john@example.com",
};

只读属性

接口的属性可以是只读的,使用 readonly 关键字标记。

typescript
interface Person {
  readonly id: number;
  name: string;
  age: number;
}

let person: Person = { id: 1, name: "John", age: 30 };

// 正确:修改非只读属性
person.name = "Jane";
person.age = 25;

// 错误:修改只读属性
// person.id = 2; // 无法分配到 "id" ,因为它是只读属性

函数类型

接口可以定义函数类型。

typescript
interface GreetFunction {
  (name: string, age: number): string;
}

let greet: GreetFunction = function (name: string, age: number): string {
  return `Hello, ${name}! You are ${age} years old.`;
};

console.log(greet("John", 30)); // 输出:Hello, John! You are 30 years old.

索引签名

接口可以定义索引签名,用于描述对象的索引类型和返回类型。

typescript
// 字符串索引签名
interface StringMap {
  [key: string]: string;
}

let map: StringMap = {
  name: "John",
  age: "30", // 注意:值必须是字符串
  gender: "male",
};

// 数字索引签名
interface NumberArray {
  [index: number]: number;
}

let array: NumberArray = [1, 2, 3, 4, 5];
console.log(array[0]); // 输出:1
console.log(array[1]); // 输出:2

// 混合索引签名
interface MixedMap {
  [key: string]: string | number;
  length: number; // 必须符合索引签名的类型
}

let mixed: MixedMap = {
  name: "John",
  age: 30,
  length: 2,
};

3. 接口的继承

接口可以继承其他接口,使用 extends 关键字。

单继承

typescript
interface Person {
  name: string;
  age: number;
}

interface Employee extends Person {
  employeeId: number;
  department: string;
}

let employee: Employee = {
  name: "John",
  age: 30,
  employeeId: 1001,
  department: "IT",
};

多继承

接口可以继承多个接口,使用逗号分隔。

typescript
interface Person {
  name: string;
  age: number;
}

interface Address {
  street: string;
  city: string;
  country: string;
}

interface Employee extends Person, Address {
  employeeId: number;
  department: string;
}

let employee: Employee = {
  name: "John",
  age: 30,
  street: "123 Main St",
  city: "New York",
  country: "USA",
  employeeId: 1001,
  department: "IT",
};

4. 接口的实现

类可以实现接口,使用 implements 关键字。

typescript
interface Person {
  name: string;
  age: number;
  greet(): string;
}

class Employee implements Person {
  constructor(
    public name: string,
    public age: number,
    public employeeId: number,
  ) {}

  greet(): string {
    return `Hello, my name is ${this.name} and I am ${this.age} years old.`;
  }
}

let employee = new Employee("John", 30, 1001);
console.log(employee.greet()); // 输出:Hello, my name is John and I am 30 years old.

实现多个接口

类可以实现多个接口,使用逗号分隔。

typescript
interface Person {
  name: string;
  age: number;
}

interface Greetable {
  greet(): string;
}

class Employee implements Person, Greetable {
  constructor(
    public name: string,
    public age: number,
    public employeeId: number,
  ) {}

  greet(): string {
    return `Hello, my name is ${this.name} and I am ${this.age} years old.`;
  }
}

let employee = new Employee("John", 30, 1001);
console.log(employee.greet()); // 输出:Hello, my name is John and I am 30 years old.

5. 接口的高级用法

接口合并

多个同名接口会自动合并。

typescript
interface Person {
  name: string;
  age: number;
}

interface Person {
  gender?: string;
  email?: string;
}

// 合并后的接口
// interface Person {
//   name: string;
//   age: number;
//   gender?: string;
//   email?: string;
// }

let person: Person = {
  name: "John",
  age: 30,
  gender: "male",
  email: "john@example.com",
};

接口与类型别名

接口和类型别名都可以用于定义类型,但它们有一些区别:

  • 接口只能用于定义对象类型,而类型别名可以用于定义任何类型。
  • 接口可以被继承和实现,而类型别名不能。
  • 接口可以自动合并,而类型别名不能。
typescript
// 接口
interface Person {
  name: string;
  age: number;
}

// 类型别名
type PersonType = {
  name: string;
  age: number;
};

// 类型别名可以定义联合类型
type StringOrNumber = string | number;

// 类型别名可以定义元组类型
type PersonTuple = [string, number];

接口与泛型

接口可以使用泛型,使接口更加灵活。

typescript
interface Container<T> {
  value: T;
  getValue(): T;
  setValue(value: T): void;
}

class NumberContainer implements Container<number> {
  constructor(public value: number) {}

  getValue(): number {
    return this.value;
  }

  setValue(value: number): void {
    this.value = value;
  }
}

class StringContainer implements Container<string> {
  constructor(public value: string) {}

  getValue(): string {
    return this.value;
  }

  setValue(value: string): void {
    this.value = value;
  }
}

let numberContainer = new NumberContainer(10);
console.log(numberContainer.getValue()); // 输出:10
numberContainer.setValue(20);
console.log(numberContainer.getValue()); // 输出:20

let stringContainer = new StringContainer("hello");
console.log(stringContainer.getValue()); // 输出:hello
stringContainer.setValue("world");
console.log(stringContainer.getValue()); // 输出:world

6. 接口的最佳实践

1. 使用接口定义对象结构

使用接口来定义对象的结构,提高代码的可读性和类型安全性。

typescript
// 推荐:使用接口
interface User {
  id: number;
  name: string;
  email: string;
}

function getUser(id: number): User {
  // 实现
}

// 不推荐:直接使用类型字面量
function getUser(id: number): { id: number; name: string; email: string } {
  // 实现
}

2. 合理使用可选属性

对于可能不存在的属性,使用可选属性。

typescript
// 推荐:使用可选属性
interface User {
  id: number;
  name: string;
  email?: string;
  age?: number;
}

// 不推荐:使用联合类型
interface User {
  id: number;
  name: string;
  email: string | undefined;
  age: number | undefined;
}

3. 使用只读属性

对于不应该被修改的属性,使用只读属性。

typescript
// 推荐:使用只读属性
interface User {
  readonly id: number;
  name: string;
  email: string;
}

// 不推荐:使用普通属性
interface User {
  id: number;
  name: string;
  email: string;
}

4. 合理使用接口继承

对于相关的接口,使用继承来减少代码重复。

typescript
// 推荐:使用接口继承
interface Person {
  name: string;
  age: number;
}

interface Employee extends Person {
  employeeId: number;
  department: string;
}

// 不推荐:重复定义属性
interface Person {
  name: string;
  age: number;
}

interface Employee {
  name: string;
  age: number;
  employeeId: number;
  department: string;
}

5. 使用接口实现多态

使用接口实现多态,提高代码的灵活性。

typescript
interface Shape {
  area(): number;
}

class Circle implements Shape {
  constructor(private radius: number) {}

  area(): number {
    return Math.PI * this.radius * this.radius;
  }
}

class Rectangle implements Shape {
  constructor(
    private width: number,
    private height: number,
  ) {}

  area(): number {
    return this.width * this.height;
  }
}

function calculateArea(shape: Shape): number {
  return shape.area();
}

let circle = new Circle(5);
let rectangle = new Rectangle(4, 6);

console.log(calculateArea(circle)); // 输出:78.53981633974483
console.log(calculateArea(rectangle)); // 输出:24

7. 常见错误

1. 缺少必选属性

当使用接口时,必须提供所有必选属性。

typescript
interface Person {
  name: string;
  age: number;
}

// 错误:缺少必选属性
// let person: Person = { name: "John" }; // 缺少属性 "age"

// 正确
let person: Person = { name: "John", age: 30 };

2. 多余属性

当使用对象字面量赋值给接口类型时,不能包含接口中未定义的属性。

typescript
interface Person {
  name: string;
  age: number;
}

// 错误:多余属性
// let person: Person = { name: "John", age: 30, gender: "male" }; // 对象字面量只能指定已知属性

// 正确:使用类型断言
let person: Person = { name: "John", age: 30, gender: "male" } as Person;

// 正确:使用变量
let personObj = { name: "John", age: 30, gender: "male" };
let person: Person = personObj;

3. 修改只读属性

只读属性不能被修改。

typescript
interface Person {
  readonly id: number;
  name: string;
  age: number;
}

let person: Person = { id: 1, name: "John", age: 30 };

// 错误:修改只读属性
// person.id = 2; // 无法分配到 "id" ,因为它是只读属性

// 正确:修改非只读属性
person.name = "Jane";
person.age = 25;

4. 接口实现错误

类实现接口时,必须实现接口中的所有必选属性和方法。

typescript
interface Person {
  name: string;
  age: number;
  greet(): string;
}

// 错误:缺少 greet 方法
// class Employee implements Person {
//   constructor(public name: string, public age: number) {}
// }

// 正确
class Employee implements Person {
  constructor(
    public name: string,
    public age: number,
  ) {}

  greet(): string {
    return `Hello, my name is ${this.name}`;
  }
}

5. 接口继承错误

接口继承时,不能与父接口的属性类型冲突。

typescript
interface Person {
  name: string;
  age: number;
}

// 错误:属性类型冲突
// interface Employee extends Person {
//   name: number; // 类型 'number' 不能赋值给类型 'string'
//   employeeId: number;
// }

// 正确
interface Employee extends Person {
  employeeId: number;
  department: string;
}

8. 实际应用场景

1. 定义 API 响应类型

typescript
interface User {
  id: number;
  name: string;
  email: string;
  createdAt: string;
  updatedAt: string;
}

interface ApiResponse<T> {
  data: T;
  status: number;
  message: string;
}

// 使用
async function fetchUser(id: number): Promise<ApiResponse<User>> {
  const response = await fetch(`/api/users/${id}`);
  return response.json();
}

fetchUser(1).then((response) => {
  console.log(response.data.name);
  console.log(response.status);
  console.log(response.message);
});

2. 定义配置对象

typescript
interface DatabaseConfig {
  host: string;
  port: number;
  username: string;
  password: string;
  database: string;
  ssl?: boolean;
  poolSize?: number;
}

function createDatabaseConnection(config: DatabaseConfig): void {
  console.log(
    `Connecting to database ${config.database} on ${config.host}:${config.port}`,
  );
  // 实现连接逻辑
}

const config: DatabaseConfig = {
  host: "localhost",
  port: 5432,
  username: "postgres",
  password: "password",
  database: "mydb",
  ssl: false,
  poolSize: 10,
};

createDatabaseConnection(config);

3. 定义函数类型

typescript
interface ValidateFunction<T> {
  (value: T): boolean;
}

const validateEmail: ValidateFunction<string> = (email: string): boolean => {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return emailRegex.test(email);
};

const validateAge: ValidateFunction<number> = (age: number): boolean => {
  return age >= 18 && age <= 120;
};

console.log(validateEmail("john@example.com")); // 输出:true
console.log(validateEmail("invalid-email")); // 输出:false
console.log(validateAge(25)); // 输出:true
console.log(validateAge(15)); // 输出:false

4. 定义组件 props

在 React 或 Vue 等框架中,使用接口定义组件的 props 类型。

typescript
// React 组件 props
interface ButtonProps {
  text: string;
  onClick: () => void;
  variant?: "primary" | "secondary" | "danger";
  disabled?: boolean;
}

function Button({ text, onClick, variant = "primary", disabled = false }: ButtonProps) {
  return (
    <button
      onClick={onClick}
      className={`btn btn-${variant}`}
      disabled={disabled}
    >
      {text}
    </button>
  );
}

// Vue 组件 props
interface UserCardProps {
  user: {
    id: number;
    name: string;
    email: string;
  };
  showEmail?: boolean;
}

// 在 Vue 组件中使用
// props: {
//   user: {
//     type: Object as () => User,
//     required: true
//   },
//   showEmail: {
//     type: Boolean,
//     default: false
//   }
// }

5. 定义工具函数的参数和返回值

typescript
interface SortOptions {
  direction: "asc" | "desc";
  compareFn?: (a: any, b: any) => number;
}

interface SortResult<T> {
  sortedArray: T[];
  originalArray: T[];
  sortTime: number;
}

function sortArray<T>(array: T[], options: SortOptions): SortResult<T> {
  const startTime = performance.now();
  const originalArray = [...array];
  const sortedArray = array.sort(
    options.compareFn ||
      ((a, b) => {
        if (a < b) return options.direction === "asc" ? -1 : 1;
        if (a > b) return options.direction === "asc" ? 1 : -1;
        return 0;
      }),
  );
  const sortTime = performance.now() - startTime;

  return {
    sortedArray,
    originalArray,
    sortTime,
  };
}

const numbers = [3, 1, 4, 1, 5, 9, 2, 6];
const result = sortArray(numbers, { direction: "asc" });
console.log(result.sortedArray); // 输出:[1, 1, 2, 3, 4, 5, 6, 9]
console.log(result.sortTime); // 输出:排序时间(毫秒)

总结

TypeScript 中的接口是一种强大的类型定义机制,它用于描述对象的结构和类型。它包括:

  • 基本用法:声明接口,使用接口进行类型注解
  • 接口的属性:可选属性,只读属性,函数类型,索引签名
  • 接口的继承:单继承,多继承
  • 接口的实现:类实现接口,实现多个接口
  • 接口的高级用法:接口合并,接口与类型别名,接口与泛型
  • 最佳实践:使用接口定义对象结构,合理使用可选属性和只读属性,合理使用接口继承,使用接口实现多态
  • 常见错误:缺少必选属性,多余属性,修改只读属性,接口实现错误,接口继承错误
  • 实际应用场景:定义 API 响应类型,定义配置对象,定义函数类型,定义组件 props,定义工具函数的参数和返回值

通过合理使用接口,你可以在 TypeScript 中更清晰地定义对象的结构和类型,提高代码的可读性和类型安全性,使代码更加健壮和可维护。