TypeScript高级特性详解
本文深入浅出地讲解 TypeScript 的高级特性。我们会从实际问题出发,循序渐进地介绍每个概念,让你真正理解这些高级特性的用途和使用方法。
# 一、为什么需要高级类型?
在学习具体特性之前,先理解我们要解决什么问题。
假设你在开发一个 API 客户端,需要处理不同的响应状态:
// 不好的设计:使用联合类型但没有类型区分
interface ApiResponse {
status: 'success' | 'error' | 'loading';
data?: any;
error?: string;
}
function handleResponse(response: ApiResponse) {
if (response.status === 'success') {
console.log(response.data.toUpperCase()); // 危险!data 可能不存在
}
}
问题在于:
- TypeScript 无法知道
status为'success'时,data一定存在 - 我们需要更精确的类型系统来表达这种关系
这就是高级类型要解决的问题。
# 二、交叉类型(&):组合多个类型
# 基本概念
交叉类型就像"并集",把多个类型的属性合并到一起:
interface HasName {
name: string;
}
interface HasAge {
age: number;
}
// Person 必须同时拥有 name 和 age
type Person = HasName & HasAge;
const person: Person = {
name: 'Alice',
age: 30
};
类比理解:就像一个人既是工程师(有编程技能)又是设计师(有设计技能),交叉类型表示"同时是"。
# 实际应用:Mixin 模式
在类中混入多个功能:
// 时间戳功能
interface Timestamped {
createdAt: Date;
updatedAt: Date;
}
// 软删除功能
interface SoftDeletable {
isDeleted: boolean;
deletedAt?: Date;
}
// 用户数据
interface UserData {
id: number;
name: string;
}
// 组合所有功能
type User = UserData & Timestamped & SoftDeletable;
const user: User = {
id: 1,
name: 'Alice',
createdAt: new Date(),
updatedAt: new Date(),
isDeleted: false
};
# 注意事项
交叉类型处理冲突属性时会变成 never:
interface A {
value: string;
}
interface B {
value: number;
}
type C = A & B; // { value: never },因为 value 不能同时是 string 和 number
# 三、可辨识联合类型:解决开头的问题
# 什么是可辨识联合?
通过一个公共的字面量属性来区分不同的类型:
// 每个接口都有 type 属性,但值不同
interface Success {
type: 'success';
data: string;
}
interface Error {
type: 'error';
errorMessage: string;
}
interface Loading {
type: 'loading';
}
// 联合类型
type ApiResponse = Success | Error | Loading;
# 类型收窄(Type Narrowing)
TypeScript 会根据 type 字段自动推断具体类型:
function handleResponse(response: ApiResponse) {
switch (response.type) {
case 'success':
// 在这里,TypeScript 知道 response 是 Success 类型
console.log(response.data.toUpperCase()); // 安全!
// console.log(response.errorMessage); // 错误:Success 没有 errorMessage
break;
case 'error':
// 在这里,TypeScript 知道 response 是 Error 类型
console.log(response.errorMessage); // 安全!
// console.log(response.data); // 错误:Error 没有 data
break;
case 'loading':
console.log('Loading...');
break;
}
}
关键点:
type属性是字面量类型('success'而非string)- TypeScript 通过
type的值判断具体是哪个类型 - 这种模式在 Redux、状态机等场景中非常常见
# 实际应用:React 组件状态
interface IdleState {
status: 'idle';
}
interface LoadingState {
status: 'loading';
progress: number;
}
interface SuccessState {
status: 'success';
data: string[];
}
interface ErrorState {
status: 'error';
error: Error;
}
type DataState = IdleState | LoadingState | SuccessState | ErrorState;
function renderUI(state: DataState) {
switch (state.status) {
case 'idle':
return 'Click to load';
case 'loading':
return `Loading... ${state.progress}%`; // 可以访问 progress
case 'success':
return state.data.join(', '); // 可以访问 data
case 'error':
return `Error: ${state.error.message}`; // 可以访问 error
}
}
# 四、类型索引:从已有类型提取信息
# keyof:获取所有键
interface User {
id: number;
name: string;
email: string;
}
type UserKeys = keyof User; // 'id' | 'name' | 'email'
实际应用:编写通用的属性访问函数
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user: User = { id: 1, name: 'Alice', email: 'alice@example.com' };
const name = getProperty(user, 'name'); // TypeScript 知道返回 string
const id = getProperty(user, 'id'); // TypeScript 知道返回 number
// const age = getProperty(user, 'age'); // 错误:'age' 不存在
# 索引访问类型:提取属性类型
interface User {
id: number;
name: string;
address: {
city: string;
country: string;
};
}
type NameType = User['name']; // string
type AddressType = User['address']; // { city: string; country: string; }
type CityType = User['address']['city']; // string
// 提取多个属性的类型(返回联合类型)
type NameOrId = User['name' | 'id']; // string | number
实际应用:从数组提取元素类型
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
];
type User = typeof users[number]; // { id: number; name: string; }
# typeof:从值获取类型
const config = {
apiUrl: 'https://api.example.com',
timeout: 5000,
retries: 3
};
type Config = typeof config;
// { apiUrl: string; timeout: number; retries: number; }
// 实际应用:从函数获取返回值类型
function createUser(name: string, age: number) {
return { name, age, createdAt: new Date() };
}
type User = ReturnType<typeof createUser>;
// { name: string; age: number; createdAt: Date; }
# 五、条件类型(T extends U ? X : Y)
# 基本概念
条件类型就像三元运算符,根据类型关系返回不同类型:
// 如果 T 是 string,返回 'yes',否则返回 'no'
type IsString<T> = T extends string ? 'yes' : 'no';
type A = IsString<string>; // 'yes'
type B = IsString<number>; // 'no'
type C = IsString<any>; // 'yes'(any 可以赋值给任何类型)
类比理解:就像 const result = condition ? 'yes' : 'no',但是在类型层面。
# 实际应用 1:NonNullable
去除 null 和 undefined:
type NonNullable<T> = T extends null | undefined ? never : T;
type A = NonNullable<string | null | undefined>; // string
type B = NonNullable<number | null>; // number
# 实际应用 2:函数重载的智能提示
// 根据参数类型返回不同类型
type ReturnValue<T> = T extends 'string'
? string
: T extends 'number'
? number
: never;
function getValue<T extends 'string' | 'number'>(type: T): ReturnValue<T> {
if (type === 'string') {
return 'hello' as ReturnValue<T>;
} else {
return 42 as ReturnValue<T>;
}
}
const str = getValue('string'); // string
const num = getValue('number'); // number
# 分布式条件类型
当条件类型遇到联合类型时,会分别应用到每个成员:
type ToArray<T> = T extends any ? T[] : never;
type Result = ToArray<string | number>;
// 展开过程:
// ToArray<string> | ToArray<number>
// string[] | number[]
实际应用:提取联合类型中的某些成员
type ExtractString<T> = T extends string ? T : never;
type Result = ExtractString<string | number | boolean>; // string
# 六、infer:在条件类型中推断类型
# 基本概念
infer 关键字让我们在条件类型中"捕获"某个类型:
// 提取数组元素类型
type ArrayElement<T> = T extends (infer E)[] ? E : never;
type A = ArrayElement<string[]>; // string
type B = ArrayElement<number[]>; // number
type C = ArrayElement<string>; // never(不是数组)
理解步骤:
- 检查
T是否是数组类型(infer E)[] - 如果是,
infer E会"推断"出元素类型,并赋值给E - 返回推断出的
E
# 实际应用 1:提取函数返回值类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function getUser() {
return { id: 1, name: 'Alice' };
}
type User = ReturnType<typeof getUser>; // { id: number; name: string; }
解读:
(...args: any[])匹配任意函数infer R推断返回值类型- 返回推断出的
R
# 实际应用 2:提取函数参数类型
type Parameters<T> = T extends (...args: infer P) => any ? P : never;
function createUser(name: string, age: number) {
return { name, age };
}
type Params = Parameters<typeof createUser>; // [name: string, age: number]
# 实际应用 3:提取 Promise 值类型
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type A = UnwrapPromise<Promise<string>>; // string
type B = UnwrapPromise<number>; // number
// 递归提取嵌套 Promise
type DeepUnwrapPromise<T> = T extends Promise<infer U>
? DeepUnwrapPromise<U>
: T;
type C = DeepUnwrapPromise<Promise<Promise<string>>>; // string
# 实际应用 4:提取元组的第一个和最后一个元素
// 提取第一个元素
type First<T extends any[]> = T extends [infer F, ...any[]] ? F : never;
type A = First<[string, number, boolean]>; // string
// 提取最后一个元素
type Last<T extends any[]> = T extends [...any[], infer L] ? L : never;
type B = Last<[string, number, boolean]>; // boolean
# 七、映射类型:批量转换属性
# 基本概念
映射类型可以批量修改对象的所有属性:
interface User {
id: number;
name: string;
email: string;
}
// 将所有属性变为只读
type ReadonlyUser = {
readonly [K in keyof User]: User[K];
};
// 等价于:
// {
// readonly id: number;
// readonly name: string;
// readonly email: string;
// }
语法解读:
K in keyof User:遍历 User 的所有键('id' | 'name' | 'email')User[K]:获取对应的值类型
# 内置映射类型
TypeScript 提供了常用的映射类型:
// 1. Partial:所有属性变为可选
type PartialUser = Partial<User>;
// {
// id?: number;
// name?: string;
// email?: string;
// }
// 2. Required:所有属性变为必需
type RequiredUser = Required<PartialUser>;
// 3. Readonly:所有属性变为只读
type ReadonlyUser = Readonly<User>;
// 4. Pick:选取部分属性
type UserPreview = Pick<User, 'id' | 'name'>;
// { id: number; name: string; }
// 5. Omit:排除部分属性
type PublicUser = Omit<User, 'email'>;
// { id: number; name: string; }
# 修饰符操作
// 移除 readonly 修饰符
type Mutable<T> = {
-readonly [K in keyof T]: T[K];
};
// 移除可选修饰符
type Concrete<T> = {
[K in keyof T]-?: T[K];
};
interface User {
readonly id: number;
name?: string;
}
type MutableUser = Mutable<User>; // { id: number; name?: string; }
type ConcreteUser = Concrete<User>; // { readonly id: number; name: string; }
# 键名重映射(as 子句)
可以在映射时修改键名:
// 从属性名生成 getter 方法
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
interface User {
name: string;
age: number;
}
type UserGetters = Getters<User>;
// {
// getName: () => string;
// getAge: () => number;
// }
// 过滤属性
type RemoveId<T> = {
[K in keyof T as Exclude<K, 'id'>]: T[K];
};
type UserWithoutId = RemoveId<User>; // { name: string; age: number; }
# 实际应用:深度只读
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object
? DeepReadonly<T[K]>
: T[K];
};
interface User {
name: string;
address: {
city: string;
country: string;
};
}
type ReadonlyUser = DeepReadonly<User>;
// {
// readonly name: string;
// readonly address: {
// readonly city: string;
// readonly country: string;
// };
// }
# 八、模板字面量类型
# 基本用法
在类型中使用模板字符串:
type World = "world";
type Greeting = `hello ${World}`; // "hello world"
// 联合类型会展开
type Color = "red" | "blue";
type Size = "small" | "large";
type ColoredSize = `${Color}-${Size}`;
// "red-small" | "red-large" | "blue-small" | "blue-large"
# 内置字符串操作类型
TypeScript 提供了四个内置的字符串操作类型:
// 1. Uppercase:转大写
type Upper = Uppercase<"hello">; // "HELLO"
// 2. Lowercase:转小写
type Lower = Lowercase<"HELLO">; // "hello"
// 3. Capitalize:首字母大写
type Cap = Capitalize<"hello">; // "Hello"
// 4. Uncapitalize:首字母小写
type Uncap = Uncapitalize<"Hello">; // "hello"
# 实际应用:生成方法名
// 从属性名自动生成 setter 方法名
type Setters<T> = {
[K in keyof T as `set${Capitalize<string & K>}`]: (value: T[K]) => void;
};
interface User {
name: string;
age: number;
}
type UserSetters = Setters<User>;
// {
// setName: (value: string) => void;
// setAge: (value: number) => void;
// }
# 实际应用:事件监听
type EventListener<T> = {
on<K extends string & keyof T>(
event: `${K}Changed`,
callback: (newValue: T[K]) => void
): void;
};
declare function watch<T>(obj: T): T & EventListener<T>;
const user = watch({
name: 'Alice',
age: 30
});
// 正确:监听 nameChanged 事件
user.on('nameChanged', (newName) => {
console.log(newName.toUpperCase()); // newName 是 string
});
// 正确:监听 ageChanged 事件
user.on('ageChanged', (newAge) => {
console.log(newAge + 1); // newAge 是 number
});
// 错误:事件名不对
// user.on('name', () => {});
# 九、类型编程实战
# 场景 1:实现 DeepPartial
让对象的所有层级都变为可选:
type DeepPartial<T> = T extends object
? {
[K in keyof T]?: DeepPartial<T[K]>;
}
: T;
interface User {
id: number;
profile: {
name: string;
address: {
city: string;
country: string;
};
};
}
type PartialUser = DeepPartial<User>;
const user: PartialUser = {
profile: {
address: {
city: 'Beijing' // 只提供部分字段
}
}
};
# 场景 2:提取对象中所有字符串属性
type StringKeys<T> = {
[K in keyof T]: T[K] extends string ? K : never;
}[keyof T];
interface User {
id: number;
name: string;
email: string;
age: number;
}
type UserStringKeys = StringKeys<User>; // 'name' | 'email'
理解步骤:
{ [K in keyof T]: T[K] extends string ? K : never }生成:{ id: never; name: 'name'; email: 'email'; age: never; }[keyof T]提取所有值:never | 'name' | 'email' | never- 联合类型中的
never会被自动忽略,得到'name' | 'email'
# 场景 3:扁平化嵌套数组类型
type Flatten<T> = T extends Array<infer U>
? U extends Array<any>
? Flatten<U>
: U
: T;
type Nested = [1, [2, [3, [4]]]];
type Flat = Flatten<Nested>; // 1 | 2 | 3 | 4
# 场景 4:联合类型转元组
// 这是一个高级技巧,理解起来较复杂
type UnionToIntersection<U> = (
U extends any ? (x: U) => void : never
) extends (x: infer I) => void
? I
: never;
type LastInUnion<U> = UnionToIntersection<
U extends any ? (x: U) => void : never
> extends (x: infer L) => void
? L
: never;
type UnionToTuple<U, Last = LastInUnion<U>> = [U] extends [never]
? []
: [...UnionToTuple<Exclude<U, Last>>, Last];
type Result = UnionToTuple<'a' | 'b' | 'c'>; // ['a', 'b', 'c']
说明:这个例子较为复杂,主要展示 TypeScript 类型系统的强大表达能力,实际开发中较少用到。
# 十、装饰器(Experimental)
# 什么是装饰器?
装饰器是一种特殊的声明,可以附加到类、方法、属性或参数上,用于修改它们的行为。
启用装饰器:在 tsconfig.json 中添加:
{
"compilerOptions": {
"experimentalDecorators": true
}
}
# 类装饰器
// 密封类,防止添加新属性
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@sealed
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
}
const greeter = new Greeter('Hello');
// (greeter as any).newProp = 'test'; // 运行时错误(严格模式下)
# 方法装饰器
// 性能监控装饰器
function measure(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
const start = performance.now();
const result = originalMethod.apply(this, args);
const end = performance.now();
console.log(`${propertyKey} 耗时: ${(end - start).toFixed(2)}ms`);
return result;
};
}
class Calculator {
@measure
fibonacci(n: number): number {
if (n <= 1) return n;
return this.fibonacci(n - 1) + this.fibonacci(n - 2);
}
}
const calc = new Calculator();
calc.fibonacci(30); // 输出:fibonacci 耗时: 5.23ms
# 属性装饰器
// 验证装饰器
function Min(min: number) {
return function(target: any, propertyKey: string) {
let value: number;
Object.defineProperty(target, propertyKey, {
get() {
return value;
},
set(newValue: number) {
if (newValue < min) {
throw new Error(`${propertyKey} 不能小于 ${min}`);
}
value = newValue;
}
});
};
}
class Product {
@Min(0)
price: number = 0;
}
const product = new Product();
product.price = 100; // 正确
// product.price = -10; // 运行时错误
# 实际应用:自动记录日志
function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
console.log(`调用 ${propertyKey},参数:`, args);
const result = originalMethod.apply(this, args);
console.log(`${propertyKey} 返回:`, result);
return result;
};
}
class UserService {
@log
createUser(name: string, age: number) {
return { id: 1, name, age };
}
}
const service = new UserService();
service.createUser('Alice', 30);
// 输出:
// 调用 createUser,参数: ['Alice', 30]
// createUser 返回: { id: 1, name: 'Alice', age: 30 }
# 十一、声明文件(.d.ts)
# 为第三方库添加类型
假设你使用了一个没有类型定义的 JavaScript 库:
// 创建 lodash.d.ts
declare module 'lodash' {
export function chunk<T>(array: T[], size: number): T[][];
export function compact<T>(array: T[]): T[];
}
// 现在可以有类型提示了
import { chunk } from 'lodash';
const result = chunk([1, 2, 3, 4], 2); // number[][]
# 扩展全局对象
// global.d.ts
declare global {
interface Window {
myApp: {
version: string;
init: () => void;
};
}
}
export {}; // 必须有这行,否则不会被视为模块
// 使用
window.myApp.version = '1.0.0';
window.myApp.init();
# 扩展现有模块
// 扩展 Express Request 对象
import 'express';
declare module 'express' {
interface Request {
user?: {
id: number;
name: string;
};
}
}
// 使用
import express from 'express';
const app = express();
app.get('/', (req, res) => {
if (req.user) {
res.send(`Hello, ${req.user.name}`);
}
});
# 十二、实用工具类型总结
# 对象相关
// Partial:所有属性可选
type Partial<T> = { [K in keyof T]?: T[K] };
// Required:所有属性必需
type Required<T> = { [K in keyof T]-?: T[K] };
// Readonly:所有属性只读
type Readonly<T> = { readonly [K in keyof T]: T[K] };
// Pick:选取属性
type Pick<T, K extends keyof T> = { [P in K]: T[P] };
// Omit:排除属性
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
// Record:创建对象类型
type Record<K extends string | number | symbol, T> = { [P in K]: T };
# 联合类型相关
// Exclude:从联合类型中排除
type Exclude<T, U> = T extends U ? never : T;
// Extract:从联合类型中提取
type Extract<T, U> = T extends U ? T : never;
// NonNullable:排除 null 和 undefined
type NonNullable<T> = T extends null | undefined ? never : T;
# 函数相关
// ReturnType:提取返回值类型
type ReturnType<T extends (...args: any) => any> =
T extends (...args: any) => infer R ? R : any;
// Parameters:提取参数类型
type Parameters<T extends (...args: any) => any> =
T extends (...args: infer P) => any ? P : never;
// ConstructorParameters:提取构造函数参数类型
type ConstructorParameters<T extends abstract new (...args: any) => any> =
T extends abstract new (...args: infer P) => any ? P : never;
// InstanceType:提取构造函数返回类型
type InstanceType<T extends abstract new (...args: any) => any> =
T extends abstract new (...args: any) => infer R ? R : any;
# 十三、最佳实践
# 1. 优先使用 interface 而非 type
// ✅ 推荐
interface User {
id: number;
name: string;
}
// interface 可以声明合并
interface User {
email: string;
}
// ❌ type 不能重复声明
type User = {
id: number;
name: string;
};
何时使用 type:
- 联合类型:
type ID = string | number - 元组:
type Point = [number, number] - 映射类型:
type Readonly<T> = { readonly [K in keyof T]: T[K] }
# 2. 避免过度使用 any
// ❌ 避免
function process(data: any) {
return data.value;
}
// ✅ 使用 unknown(需要类型检查)
function process(data: unknown) {
if (typeof data === 'object' && data !== null && 'value' in data) {
return (data as { value: any }).value;
}
}
// ✅ 使用泛型
function process<T extends { value: any }>(data: T) {
return data.value;
}
# 3. 使用 const 断言
// ❌ 类型过于宽泛
const routes = {
home: '/',
about: '/about'
};
// routes: { home: string; about: string; }
// ✅ 使用 const 断言
const routes = {
home: '/',
about: '/about'
} as const;
// routes: { readonly home: "/"; readonly about: "/about"; }
# 4. 善用类型守卫
function isString(value: unknown): value is string {
return typeof value === 'string';
}
function process(value: unknown) {
if (isString(value)) {
console.log(value.toUpperCase()); // value 是 string
}
}
# 5. 合理使用泛型约束
// ❌ 太宽泛
function merge<T, U>(obj1: T, obj2: U) {
return { ...obj1, ...obj2 };
}
// ✅ 约束为对象类型
function merge<T extends object, U extends object>(obj1: T, obj2: U) {
return { ...obj1, ...obj2 };
}
merge({ a: 1 }, { b: 2 }); // 正确
// merge(1, 2); // 错误
# 十四、总结
TypeScript 的高级特性虽然看起来复杂,但都是为了解决实际问题:
- 交叉类型(&):组合多个类型
- 可辨识联合:安全地处理不同状态
- 类型索引(keyof、typeof):从已有类型提取信息
- 条件类型:根据条件返回不同类型
- infer 关键字:在条件类型中推断类型
- 映射类型:批量转换对象属性
- 模板字面量类型:操作字符串类型
- 装饰器:修改类和方法的行为
掌握这些特性后,你可以:
- 编写更加类型安全的代码
- 减少运行时错误
- 获得更好的 IDE 智能提示
- 构建更灵活的类型系统
建议循序渐进地学习,从实际问题出发,逐步掌握这些高级特性。结合 TypeScript极简入门,你将能够充分发挥 TypeScript 的强大能力。
祝你变得更强!