Appearance
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()); // 输出:world6. 接口的最佳实践
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)); // 输出:247. 常见错误
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)); // 输出:false4. 定义组件 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 中更清晰地定义对象的结构和类型,提高代码的可读性和类型安全性,使代码更加健壮和可维护。