第 96 章:对象类型与类型别名
学习目标
- 掌握对象类型的多种定义方式
- 学会用
type给复杂类型起名 - 理解类型别名的应用场景
- 区分
type与interface的使用边界
一、对象类型的三种写法
1.1 对象字面量直接标注
typescript
function greet(user: { name: string; age: number }) {
console.log(`Hello, ${user.name} (${user.age})`);
}
greet({ name: 'Tom', age: 18 });适用场景:简单的、一次性的对象。
1.2 内联复杂结构
typescript
function createUser(
data: { name: string; email: string; age?: number }
): { id: number; name: string; email: string } {
return {
id: Date.now(),
name: data.name,
email: data.email
};
}问题:参数和返回值都是对象字面量,重复且难以维护。
1.3 提取为类型(推荐)
typescript
interface User {
id: number;
name: string;
email: string;
age?: number;
}
function createUser(data: Omit<User, 'id'>): User {
return {
id: Date.now(),
name: data.name,
email: data.email
};
}二、type 关键字(类型别名)
2.1 基本语法
typescript
type User = {
id: number;
name: string;
email: string;
};
let u: User = { id: 1, name: 'Tom', email: 'tom@example.com' };本质:type 给一个类型起一个新名字,不创建新类型。
2.2 别名 vs 原始类型
typescript
type ID = number; // ID 就是 number 的别名
type Username = string;
let userId: ID = 123;
let name: Username = 'Tom';
// 完全等价
let a: ID = 456;
let b: number = 456;
a = b; // ✅ 类型相同2.3 别名 vs 联合/交叉
typescript
// 联合
type Status = 'pending' | 'success' | 'error';
type Result = string | number;
// 交叉
type Employee = { name: string } & { salary: number };
// 等价于 { name: string; salary: number }2.4 给函数起别名
typescript
type GreetFunction = (name: string) => string;
const greet: GreetFunction = (name) => `Hello, ${name}!`;
// 函数签名复用
const welcome: GreetFunction = (name) => `Welcome, ${name}!`;2.5 给数组/元组起别名
typescript
type StringArray = string[];
type Pair = [number, string];
type Matrix = number[][];
let arr: StringArray = ['a', 'b'];
let p: Pair = [1, 'one'];
let m: Matrix = [[1, 2], [3, 4]];三、type vs interface 核心差异
3.1 共同点
typescript
// 都能定义对象
type UserT = { name: string };
interface UserI { name: string; }
// 都能定义联合(interface 需配合 type)
interface A { a: string }
interface B { b: number }
type AB = A | B;3.2 差异一:能否定义联合/元组
typescript
// ✅ type 可以
type Status = 'pending' | 'success';
type Pair = [string, number];
// ❌ interface 不可以
// interface Status = 'pending' | 'success'; // ❌ 语法错误3.3 差异二:能否自动合并
typescript
// interface 声明合并
interface User {
name: string;
}
interface User { // ✅ 自动合并
age: number;
}
const u: User = { name: 'Tom', age: 18 };
// ❌ type 不可以重复声明
// type User = { name: string };
// type User = { age: number }; // ❌ Duplicate identifier 'User'3.4 差异三:extends 与交叉
typescript
// interface 用 extends
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
// type 用交叉
type Animal = { name: string };
type Dog = Animal & { breed: string };3.5 差异四:映射类型支持
typescript
// ❌ interface 不支持映射类型
type Readonly<T> = { readonly [K in keyof T]: T[K] };
interface User {
name: string;
age: number;
}
type ReadonlyUser = Readonly<User>; // ✅
// ❌ interface ReadonlyUser = Readonly<User>;3.6 决策表
四、对象类型的高级特性
4.1 可选属性
typescript
type User = {
name: string;
email?: string; // 可选
};
let u1: User = { name: 'Tom' }; // ✅
let u2: User = { name: 'Tom', email: 'a@b' }; // ✅4.2 只读属性
typescript
type User = {
readonly id: number;
name: string;
};
const u: User = { id: 1, name: 'Tom' };
u.name = 'Jerry'; // ✅
u.id = 2; // ❌ Cannot assign to 'id' because it is a read-only property4.3 索引签名
typescript
type Dictionary = {
[key: string]: string;
};
const dict: Dictionary = {
name: 'Tom',
email: 'tom@example.com'
};4.4 嵌套对象
typescript
type Address = {
street: string;
city: string;
zip: string;
};
type User = {
name: string;
address: Address; // 嵌套
};
const u: User = {
name: 'Tom',
address: {
street: '中关村大街 1 号',
city: '北京',
zip: '100000'
}
};4.5 数组字段
typescript
type Project = {
name: string;
members: string[];
tags: { key: string; value: string }[];
};
const p: Project = {
name: 'TaskFlow',
members: ['Tom', 'Jerry'],
tags: [
{ key: 'language', value: 'TypeScript' },
{ key: 'framework', value: 'Vue 3' }
]
};五、类型别名的实战技巧
5.1 提取公共字段
typescript
// 基础类型
type BaseEntity = {
id: number;
createdAt: Date;
updatedAt: Date;
};
// 扩展
type User = BaseEntity & {
name: string;
email: string;
};
type Product = BaseEntity & {
title: string;
price: number;
};5.2 复用函数类型
typescript
type AsyncFunction<T> = (input: T) => Promise<T>;
const fetchUser: AsyncFunction<{ id: number }> = async ({ id }) => {
// ...
return { id };
};5.3 类型映射的过渡
typescript
type User = {
name: string;
age: number;
email: string;
};
// 把所有字段变成可选
type OptionalUser = {
[K in keyof User]?: User[K];
};
type PartialUser = Partial<User>; // TS 内置工具,效果相同六、对象字面量的额外属性检查
typescript
type Config = {
host: string;
port: number;
};
function start(c: Config) { /* ... */ }
start({ host: 'localhost', port: 3000 }); // ✅
start({ host: 'localhost', port: 3000, debug: true }); // ❌ Object literal may only specify known properties绕过方式:
typescript
// 1. 用变量
const config = { host: 'localhost', port: 3000, debug: true };
start(config); // ✅
// 2. 类型断言
start({ host: 'localhost', port: 3000, debug: true } as Config); // ⚠️
// 3. 索引签名
type Config = {
host: string;
port: number;
[key: string]: any; // 允许任意额外字段
};七、TypeScript 风格的 DTO
typescript
// DTO = Data Transfer Object(数据传输对象)
type UserDTO = {
id: number;
username: string;
email: string;
role: 'admin' | 'user';
createdAt: string; // ISO 字符串
};
type CreateUserDTO = Omit<UserDTO, 'id' | 'createdAt'>;
type UpdateUserDTO = Partial<Omit<UserDTO, 'id'>>;
type UserResponse = { code: number; data: UserDTO };八、命名规范
typescript
// 类型/接口:PascalCase
type UserProfile = { ... };
interface OrderDetail { ... }
// 泛型参数:T、U、V 或 TKey、TValue
type Pair<T, U> = { first: T; second: U };
// 类型别名和值不要重名
type User = { ... };
// const User = ...; // ❌ 容易混淆九、常见错误
9.1 类型不匹配
typescript
type User = { name: string; age: number };
const u: User = { name: 'Tom', age: 'eighteen' }; // ❌ string not assignable to number9.2 缺少字段
typescript
const u: User = { name: 'Tom' }; // ❌ Property 'age' is missing9.3 多余字段(直接传字面量)
typescript
const u: User = { name: 'Tom', age: 18, extra: true }; // ❌十、本章小结
| 要点 | 关键 |
|---|---|
| 对象类型 | 三种写法:内联、提取为 interface、提取为 type |
| type 关键字 | 给类型起别名,可用于联合、元组、函数、数组 |
| type vs interface | type 适合联合/映射;interface 适合对象 + 声明合并 |
| 对象特性 | 可选 ?、只读 readonly、索引签名、嵌套 |
| DTO 模式 | 用 Omit / Partial 派生新类型 |
动手练习
- 类型设计:为一个「博客系统」设计
User、Post、Comment三个类型 - 类型派生:基于
User类型派生CreateUserDTO(无 id/createdAt)和UpdateUserDTO(所有字段可选) - type vs interface:把练习 1 的类型先用 interface 写一遍,再改成 type 写一遍,对比感受
推荐阅读
- 📖 TypeScript Handbook - Object Types — 官方对象类型
- 📖 TypeScript Handbook - Type Aliases — 类型别名
- 🌐 Type vs Interface 决策指南 — 官方对比
下一章:第 97 章:函数类型 →