接口(interface)和类型别名(type)是 TypeScript 中定义对象结构的两种核心方式。
2.1 接口(interface)
接口用于描述对象的"形状"——它有哪些属性,每个属性是什么类型:
interface User {
name: string;
age: number;
email: string;
}
// 使用接口
const user: User = {
name: "张三",
age: 25,
email: "zhangsan@example.com",
};
// ❌ 缺少属性会报错
const user2: User = {
name: "李四",
// 错误:缺少 age 和 email
};
// ❌ 多余属性也会报错
const user3: User = {
name: "王五",
age: 30,
email: "wangwu@example.com",
phone: "123456", // 错误:User 中没有 phone
};
2.2 可选属性与只读属性
interface Config {
host: string;
port: number;
readonly database: string; // 只读,赋值后不可修改
ssl?: boolean; // 可选,可以不传
timeout?: number; // 可选
}
const config: Config = {
host: "localhost",
port: 3306,
database: "mydb",
};
config.host = "127.0.0.1"; // ✅ 可以修改
config.database = "other"; // ❌ 只读属性不能修改
config.ssl; // ✅ undefined(可选属性未赋值)
2.3 索引签名
当你不确定对象有哪些属性,但知道键和值的类型时:
// 字符串索引
interface StringMap {
[key: string]: string;
}
const headers: StringMap = {
"Content-Type": "application/json",
Authorization: "Bearer xxx",
};
// 数字索引
interface NumberArray {
[index: number]: string;
}
const arr: NumberArray = ["a", "b", "c"];
混合已知属性和索引签名
interface ApiResponse {
code: number;
message: string;
[key: string]: any; // 允许额外的任意属性
}
const res: ApiResponse = {
code: 200,
message: "ok",
data: { id: 1 },
timestamp: 1234567890,
};
2.4 接口继承(extends)
接口可以继承其他接口,实现复用:
interface Animal {
name: string;
age: number;
}
interface Dog extends Animal {
breed: string;
bark(): void;
}
const dog: Dog = {
name: "旺财",
age: 3,
breed: "柴犬",
bark() {
console.log("汪汪!");
},
};
多继承
interface HasName {
name: string;
}
interface HasAge {
age: number;
}
interface HasEmail {
email: string;
}
// 同时继承多个接口
interface Person extends HasName, HasAge, HasEmail {
address?: string;
}
2.5 类型别名(type)
type 可以给任意类型起一个名字:
// 对象类型
type User = {
name: string;
age: number;
};
// 原始类型别名
type ID = string | number;
// 函数类型
type Callback = (data: string) => void;
// 元组
type Point = [number, number];
// 字面量联合类型
type Direction = "up" | "down" | "left" | "right";
使用示例
type ID = string | number;
function findUser(id: ID): User | undefined {
// ...
}
findUser(1); // ✅
findUser("abc"); // ✅
findUser(true); // ❌
2.6 interface vs type 的区别
相同点
两者都能描述对象结构:
// interface
interface UserA {
name: string;
age: number;
}
// type
type UserB = {
name: string;
age: number;
};
// 使用方式完全一样
const a: UserA = { name: "张三", age: 25 };
const b: UserB = { name: "李四", age: 30 };
不同点
1. type 能做但 interface 不能做的事:
// 联合类型
type Status = "active" | "inactive" | "deleted";
// 原始类型别名
type Name = string;
// 元组
type Pair = [string, number];
// 从已有类型计算新类型
type Partial<T> = { [K in keyof T]?: T[K] };
2. interface 能做但 type 不能做的事:
// 声明合并(同名接口会自动合并)
interface Window {
title: string;
}
interface Window {
count: number;
}
// 等同于:
// interface Window {
// title: string;
// count: number;
// }
3. 扩展方式不同:
// interface 用 extends
interface Animal {
name: string;
}
interface Dog extends Animal {
bark(): void;
}
// type 用交叉类型 &
type AnimalType = {
name: string;
};
type DogType = AnimalType & {
bark(): void;
};
选择建议
| 场景 | 推荐 |
|---|---|
| 定义对象结构 | interface(更直观,支持合并) |
| 联合类型、交叉类型 | type(只能用 type) |
| 给原始类型起别名 | type |
| 给第三方库扩展类型 | interface(利用声明合并) |
| 复杂类型计算 | type |
💡 实际开发中:两者差别不大,团队统一风格即可。常见做法是对象用 interface,其他用 type。
2.7 接口描述函数
// 方式一:接口
interface SearchFunc {
(keyword: string, page: number): Promise<string[]>;
}
// 方式二:type(更常用)
type SearchFunc2 = (keyword: string, page: number) => Promise<string[]>;
// 使用
const search: SearchFunc = async (keyword, page) => {
return ["result1", "result2"];
};
2.8 接口描述类(可调用/可构造)
// 可调用接口
interface Formatter {
(value: number): string;
}
const format: Formatter = (value) => value.toFixed(2);
// 混合类型:既是函数又有属性
interface Counter {
(): number;
count: number;
reset(): void;
}
function createCounter(): Counter {
const fn = () => fn.count++ as Counter;
fn.count = 0;
fn.reset = () => { fn.count = 0; };
return fn as Counter;
}
2.9 实用技巧
Record 快速创建映射类型
// 等同于 { [key: string]: number }
type ScoreMap = Record<string, number>;
const scores: ScoreMap = {
math: 90,
english: 85,
};
// 限制键的范围
type Role = "admin" | "user" | "guest";
type RolePermissions = Record<Role, string[]>;
const permissions: RolePermissions = {
admin: ["read", "write", "delete"],
user: ["read", "write"],
guest: ["read"],
};
用 keyof 获取接口的键
interface User {
name: string;
age: number;
email: string;
}
type UserKeys = keyof User; // "name" | "age" | "email"
function getValue(user: User, key: keyof User) {
return user[key];
}
📝 练习
- 定义一个
Product接口,包含id(number)、name(string)、price(number)、description(可选 string) - 定义
VIPProduct接口继承Product,添加discount(number) 属性 - 用 type 定义一个
ApiResult类型,可以是{ success: true; data: any }或{ success: false; error: string } - 用索引签名定义一个
Dictionary<T>泛型类型
// 参考答案
// 1
interface Product {
id: number;
name: string;
price: number;
description?: string;
}
// 2
interface VIPProduct extends Product {
discount: number;
}
// 3
type ApiResult =
| { success: true; data: any }
| { success: false; error: string };
// 4
type Dictionary<T> = {
[key: string]: T;
};
const dict: Dictionary<number> = { a: 1, b: 2 };