TypeScript 高级编程教程
面向读者:已有 TypeScript 基础(了解接口、基础类型、函数类型等),希望深入掌握类型系统高级特性与工程实践的开发者。
目录
1. 泛型深入
1.1 泛型基础回顾
泛型允许我们编写可复用的组件,这些组件可以支持多种类型而不丢失类型信息。
function identity<T>(arg: T): T {
return arg;
}
const str = identity("hello"); // 类型推断为 string
const num = identity(42); // 类型推断为 number
1.2 泛型约束
使用 extends 关键字对泛型参数进行约束,确保传入的类型满足特定条件。
// 约束 T 必须包含 length 属性
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(arg: T): T {
console.log(arg.length);
return arg;
}
logLength("hello"); // ✅ string 有 length
logLength([1, 2, 3]); // ✅ array 有 length
// logLength(123); // ❌ number 没有 length
1.3 keyof 约束
结合 keyof 操作符,可以安全地访问对象属性。
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: "Alice", age: 30 };
const name = getProperty(user, "name"); // string
const age = getProperty(user, "age"); // number
// getProperty(user, "email"); // ❌ "email" 不在 keyof 中
1.4 泛型默认值
泛型参数可以设置默认类型,类似于函数参数的默认值。
interface ApiResponse<T = unknown> {
data: T;
status: number;
message: string;
}
// 使用默认类型
const response1: ApiResponse = {
data: "anything",
status: 200,
message: "ok",
};
// 显式指定类型
interface User {
id: number;
name: string;
}
const response2: ApiResponse<User> = {
data: { id: 1, name: "Alice" },
status: 200,
message: "ok",
};
1.5 多类型参数
泛型可以接受多个类型参数,适用于需要关联多个类型的场景。
function merge<T, U>(obj1: T, obj2: U): T & U {
return { ...obj1, ...obj2 };
}
const merged = merge({ name: "Alice" }, { age: 30 });
// 类型为 { name: string } & { age: number }
// 更实用的例子:创建带默认值的对象
function createRecord<K extends string, V>(
key: K,
value: V
): Record<K, V> {
return { [key]: value } as Record<K, V>;
}
const record = createRecord("name", "Alice");
// 类型为 Record<"name", string>,即 { name: string }
1.6 泛型工具类型
TypeScript 内置了许多泛型工具类型:
// Partial - 所有属性变为可选
type PartialUser = Partial<User>;
// { id?: number; name?: string }
// Required - 所有属性变为必选
type RequiredUser = Required<PartialUser>;
// Readonly - 所有属性变为只读
type ReadonlyUser = Readonly<User>;
// Pick - 选取指定属性
type UserName = Pick<User, "name">;
// { name: string }
// Omit - 排除指定属性
type UserWithoutId = Omit<User, "id">;
// { name: string }
// Record - 构造键值对类型
type UserRoles = Record<string, string[]>;
2. 条件类型
2.1 基础条件类型
条件类型的语法类似于三元表达式:T extends U ? X : Y。
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
type C = IsString<"hello">; // true
2.2 分布式条件类型
当条件类型的 T 是联合类型时,条件类型会自动分布到每个成员上。
type ToArray<T> = T extends any ? T[] : never;
// 分布式特性:联合类型的每个成员分别计算
type Result = ToArray<string | number>;
// 等价于 ToArray<string> | ToArray<number>
// 即 string[] | number[]
// 如果不想分布,用方括号包裹
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;
type Result2 = ToArrayNonDist<string | number>;
// (string | number)[]
2.3 infer 关键字
infer 用于在条件类型中推断(提取)类型。
// 提取函数返回类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type R1 = ReturnType<() => string>; // string
type R2 = ReturnType<(x: number) => boolean>; // boolean
// 提取函数参数类型
type Parameters<T> = T extends (...args: infer P) => any ? P : never;
type P1 = Parameters<(a: string, b: number) => void>;
// [a: string, b: number]
// 提取 Promise 的内部类型
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type U1 = UnwrapPromise<Promise<string>>; // string
type U2 = UnwrapPromise<number>; // number
// 递归提取嵌套 Promise
type DeepUnwrap<T> = T extends Promise<infer U> ? DeepUnwrap<U> : T;
type U3 = DeepUnwrap<Promise<Promise<Promise<string>>>>; // string
2.4 条件类型的高级应用
// 根据参数类型决定返回类型
type Flatten<T> = T extends Array<infer U> ? U : T;
type F1 = Flatten<string[]>; // string
type F2 = Flatten<number>; // number
// 过滤联合类型
type Exclude<T, U> = T extends U ? never : T;
type E1 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type E2 = Exclude<string | number | boolean, string>; // number | boolean
// 提取联合类型中满足条件的成员
type Extract<T, U> = T extends U ? T : never;
type Ex1 = Extract<"a" | "b" | "c", "a" | "f">; // "a"
3. 映射类型
3.1 基础映射类型
映射类型基于索引签名语法,可以将现有类型的所有属性转换为新类型。
type OptionsFlags<T> = {
[K in keyof T]: boolean;
};
interface Features {
darkMode: () => void;
notifications: () => void;
}
type FeatureFlags = OptionsFlags<Features>;
// {
// darkMode: boolean;
// notifications: boolean;
// }
3.2 修饰符操作
可以在映射类型中添加或移除 readonly 和 ? 修饰符。
// 移除可选修饰符
type Concrete<T> = {
[K in keyof T]-?: T[K];
};
type MaybeUser = {
id?: number;
name?: string;
};
type DefiniteUser = Concrete<MaybeUser>;
// { id: number; name: string }
// 添加只读修饰符
type Frozen<T> = {
readonly [K in keyof T]: T[K];
};
// 移除只读修饰符
type Mutable<T> = {
-readonly [K in keyof T]: T[K];
};
3.3 键名重映射(as 子句)
TypeScript 4.1 引入了键名重映射,允许在映射过程中修改键名。
// 给所有属性添加前缀
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
interface Person {
name: string;
age: number;
}
type PersonGetters = Getters<Person>;
// {
// getName: () => string;
// getAge: () => number;
// }
// 过滤属性
type RemoveByKind<T, Kind> = {
[K in keyof T as T[K] extends Kind ? never : K]: T[K];
};
type RemoveMethods<T> = RemoveByKind<T, Function>;
interface Mixed {
id: number;
name: string;
greet: () => void;
}
type DataOnly = RemoveMethods<Mixed>;
// { id: number; name: string }
3.4 内置映射类型
TypeScript 提供了多个内置映射类型:
interface User {
name: string;
age: number;
email: string;
}
// Partial<T> - 所有属性可选
// 实现原理:
type MyPartial<T> = {
[K in keyof T]?: T[K];
};
// Required<T> - 所有属性必选
type MyRequired<T> = {
[K in keyof T]-?: T[K];
};
// Readonly<T> - 所有属性只读
type MyReadonly<T> = {
readonly [K in keyof T]: T[K];
};
// Pick<T, K> - 选取属性
type MyPick<T, K extends keyof T> = {
[P in K]: T[P];
};
// Record<K, V> - 构造类型
type MyRecord<K extends keyof any, V> = {
[P in K]: V;
};
4. 模板字面量类型
4.1 基础模板字面量类型
模板字面量类型基于字符串字面量类型,使用类似模板字符串的语法。
type Greeting = `Hello, ${string}!`;
const g1: Greeting = "Hello, World!"; // ✅
const g2: Greeting = "Hello, Alice!"; // ✅
// const g3: Greeting = "Hi, World!"; // ❌
// 联合类型的分布式组合
type Color = "red" | "blue" | "green";
type Size = "sm" | "md" | "lg";
type ColorSize = `${Color}-${Size}`;
// "red-sm" | "red-md" | "red-lg" |
// "blue-sm" | "blue-md" | "blue-lg" |
// "green-sm" | "green-md" | "green-lg"
4.2 内置字符串操作类型
type Upper = Uppercase<"hello">; // "HELLO"
type Lower = Lowercase<"HELLO">; // "hello"
type Cap = Capitalize<"hello">; // "Hello"
type Uncap = Uncapitalize<"Hello">; // "hello"
// 组合使用
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<"click">; // "onClick"
type FocusEvent = EventName<"focus">; // "onFocus"
4.3 实际应用场景
// CSS 属性类型
type CSSUnit = "px" | "em" | "rem" | "%" | "vh" | "vw";
type CSSValue = `${number}${CSSUnit}`;
const width: CSSValue = "100px";
const height: CSSValue = "50vh";
// const bad: CSSValue = "abc"; // ❌
// 路径参数提取
type ExtractRouteParams<T extends string> =
T extends `${string}:${infer Param}/${infer Rest}`
? Param | ExtractRouteParams<Rest>
: T extends `${string}:${infer Param}`
? Param
: never;
type Params = ExtractRouteParams<"/users/:userId/posts/:postId">;
// "userId" | "postId"
// 对象路径类型
type PathKeys<T, K extends keyof T = keyof T> =
K extends string
? T[K] extends object
? `${K}` | `${K}.${PathKeys<T[K]>}`
: `${K}`
: never;
interface Config {
db: {
host: string;
port: number;
};
api: {
baseUrl: string;
timeout: number;
};
}
type ConfigPath = PathKeys<Config>;
// "db" | "db.host" | "db.port" | "api" | "api.baseUrl" | "api.timeout"
4.4 与条件类型结合
// 解析路径参数并构造参数对象类型
type ParsePathParams<T extends string> =
T extends `${string}:${infer Param}/${infer Rest}`
? { [K in Param]: string } & ParsePathParams<Rest>
: T extends `${string}:${infer Param}`
? { [K in Param]: string }
: {};
type RouteParams = ParsePathParams<"/users/:userId/posts/:postId">;
// { userId: string } & { postId: string }
5. 装饰器
5.1 装饰器基础
装饰器是一种特殊的声明,可以附加到类声明、方法、访问器、属性或参数上。TypeScript 5.0+ 支持 TC39 Stage 3 标准装饰器。
// 启用 experimentalDecorators 或使用 TS 5.0+ 标准装饰器
// tsconfig.json: { "experimentalDecorators": true }
// 类装饰器
function Logger<T extends new (...args: any[]) => any>(
target: T,
context: ClassDecoratorContext
) {
return class extends target {
constructor(...args: any[]) {
super(...args);
console.log(`[${context.name}] 实例已创建`);
}
};
}
@Logger
class UserService {
constructor() {
console.log("UserService 初始化");
}
}
const service = new UserService();
// 输出: [UserService] 实例已创建
// 输出: UserService 初始化
5.2 方法装饰器
function LogExecutionTime(
target: Function,
context: ClassMethodDecoratorContext
) {
return function (this: any, ...args: any[]) {
const start = performance.now();
const result = target.call(this, ...args);
const end = performance.now();
console.log(`${String(context.name)} 执行耗时: ${(end - start).toFixed(2)}ms`);
return result;
};
}
function ValidateArgs(
target: Function,
context: ClassMethodDecoratorContext
) {
return function (this: any, ...args: any[]) {
for (const arg of args) {
if (arg === null || arg === undefined) {
throw new Error(`${String(context.name)}: 参数不能为 null 或 undefined`);
}
}
return target.call(this, ...args);
};
}
class Calculator {
@LogExecutionTime
@ValidateArgs
add(a: number, b: number): number {
// 模拟耗时操作
let sum = 0;
for (let i = 0; i < 1000000; i++) {
sum += i;
}
return a + b;
}
}
const calc = new Calculator();
calc.add(1, 2);
// 输出: add 执行耗时: x.xxms
5.3 属性装饰器与自动初始化
// 使用 TS 5.0 标准装饰器语法
function DefaultValue(value: any) {
return function <T>(
target: undefined,
context: ClassFieldDecoratorContext
) {
return function (initialValue: T) {
return initialValue ?? value;
};
};
}
function Required<T>(
target: undefined,
context: ClassFieldDecoratorContext
) {
return function (initialValue: T | undefined) {
if (initialValue === undefined) {
throw new Error(`${String(context.name)} 是必填字段`);
}
return initialValue;
};
}
class UserForm {
@DefaultValue("匿名用户")
name: string = undefined as any;
@DefaultValue(0)
age: number = undefined as any;
}
const form = new UserForm();
console.log(form.name); // "匿名用户"
console.log(form.age); // 0
5.4 装饰器工厂模式
// 创建可配置的装饰器
function Throttle(delay: number) {
return function (
target: Function,
context: ClassMethodDecoratorContext
) {
let lastCall = 0;
return function (this: any, ...args: any[]) {
const now = Date.now();
if (now - lastCall >= delay) {
lastCall = now;
return target.call(this, ...args);
}
};
};
}
function Memoize(
target: Function,
context: ClassMethodDecoratorContext
) {
const cache = new Map<string, any>();
return function (this: any, ...args: any[]) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = target.call(this, ...args);
cache.set(key, result);
return result;
};
}
class DataService {
@Throttle(1000)
handleScroll(position: number) {
console.log(`滚动到位置: ${position}`);
}
@Memoize
fibonacci(n: number): number {
if (n <= 1) return n;
return this.fibonacci(n - 1) + this.fibonacci(n - 2);
}
}
6. 模块系统
6.1 ES 模块与 CommonJS
TypeScript 同时支持 ES 模块和 CommonJS 模块系统。
// ES 模块语法(推荐)
// math.ts
export function add(a: number, b: number): number {
return a + b;
}
export default class MathHelper {
static PI = 3.14159;
}
// 导入
import MathHelper, { add } from "./math";
// CommonJS 语法
// old-module.ts
module.exports = {
helper: () => {},
};
// 导入 CommonJS 模块
import oldModule = require("./old-module");
6.2 模块解析策略
// tsconfig.json
{
"compilerOptions": {
// 模块系统
"module": "ESNext", // 推荐使用 ESNext
"moduleResolution": "bundler", // 使用打包工具时推荐
// 其他常用模块相关配置
"esModuleInterop": true, // 允许 CommonJS/ES 模块互操作
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true, // 允许导入 JSON 文件
"isolatedModules": true // 确保每个文件都可以独立编译
}
}
6.3 动态导入
// 动态导入返回 Promise
async function loadModule() {
const { add } = await import("./math");
console.log(add(1, 2));
}
// 条件导入
async function getFormatter(format: string) {
if (format === "json") {
const { JsonFormatter } = await import("./json-formatter");
return new JsonFormatter();
} else {
const { CsvFormatter } = await import("./csv-formatter");
return new CsvFormatter();
}
}
6.4 模块增强与声明合并
// 扩展第三方模块的类型
import "express";
declare module "express" {
interface Request {
user?: {
id: string;
role: string;
};
}
}
// 使用
import express from "express";
const app = express();
app.get("/", (req, res) => {
console.log(req.user?.id); // 类型安全
});
// 声明自己的模块扩展
// types/env.d.ts
declare module "*.svg" {
const content: string;
export default content;
}
declare module "*.css" {
const classes: { readonly [key: string]: string };
export default classes;
}
7. 声明文件
7.1 声明文件基础
声明文件(.d.ts)用于描述 JavaScript 库的类型信息。
// types/my-lib.d.ts
// 声明一个模块
declare module "my-lib" {
export function doSomething(input: string): number;
export interface Options {
verbose?: boolean;
timeout?: number;
}
export default function main(options: Options): void;
}
// 使用
import main, { doSomething, Options } from "my-lib";
7.2 全局声明
// types/global.d.ts
// 扩展全局作用域
declare global {
// 扩展 Window 接口
interface Window {
__APP_CONFIG__: {
apiUrl: string;
env: "development" | "production";
};
}
// 全局工具函数
function debugLog(message: string, ...args: any[]): void;
// 全局类型
type Nullable<T> = T | null;
type Optional<T> = T | undefined;
}
// 必须导出一个空对象以使其成为模块
export {};
7.3 编写高质量声明文件
// types/advanced-lib.d.ts
declare module "advanced-lib" {
// 命名空间与类型结合
namespace AdvancedLib {
interface Config {
baseUrl: string;
headers?: Record<string, string>;
interceptors?: Interceptor[];
}
interface Interceptor {
request?: (config: RequestConfig) => RequestConfig;
response?: (response: Response) => Response;
}
interface RequestConfig {
url: string;
method: string;
headers: Record<string, string>;
data?: unknown;
}
// 泛型接口
interface Client {
get<T>(url: string): Promise<T>;
post<T>(url: string, data: unknown): Promise<T>;
put<T>(url: string, data: unknown): Promise<T>;
delete<T>(url: string): Promise<T>;
}
}
// 构造函数
interface AdvancedLibConstructor {
new (config: AdvancedLib.Config): AdvancedLib.Client;
create(config: AdvancedLib.Config): AdvancedLib.Client;
}
const AdvancedLib: AdvancedLibConstructor;
export default AdvancedLib;
}
7.4 三斜线指令
/// <reference path="./types/global.d.ts" />
/// <reference types="node" />
// 三斜线指令用于声明文件间的依赖关系
// 现代项目中更推荐使用 import/export 和 tsconfig.json 的 references
8. 类型体操
8.1 DeepPartial(深度可选)
type DeepPartial<T> = T extends object
? { [K in keyof T]?: DeepPartial<T[K]> }
: T;
interface Config {
db: {
host: string;
port: number;
credentials: {
username: string;
password: string;
};
};
cache: {
ttl: number;
};
}
type PartialConfig = DeepPartial<Config>;
// 所有嵌套属性都变为可选
8.2 DeepReadonly(深度只读)
type DeepReadonly<T> = T extends object
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
: T;
type ReadonlyConfig = DeepReadonly<Config>;
// 所有嵌套属性都变为只读
8.3 UnionToIntersection(联合类型转交叉类型)
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends (k: infer I) => void
? I
: never;
type Union = { a: string } | { b: number } | { c: boolean };
type Intersection = UnionToIntersection<Union>;
// { a: string } & { b: number } & { c: boolean }
8.4 Tuple 类型操作
// 获取元组第一个元素类型
type Head<T extends readonly any[]> = T extends readonly [infer H, ...any[]]
? H
: never;
type H1 = Head<[string, number, boolean]>; // string
// 获取元组最后一个元素类型
type Last<T extends readonly any[]> = T extends readonly [...any[], infer L]
? L
: never;
type L1 = Last<[string, number, boolean]>; // boolean
// 元组反转
type Reverse<T extends readonly any[]> = T extends readonly [
infer Head,
...infer Rest,
]
? [...Reverse<Rest>, Head]
: [];
type R1 = Reverse<[1, 2, 3]>; // [3, 2, 1]
// 元组转联合
type TupleToUnion<T extends readonly any[]> = T[number];
type U1 = TupleToUnion<[string, number, boolean]>; // string | number | boolean
8.5 字符串类型操作
// 驼峰转短横线
type CamelToKebab<S extends string> =
S extends `${infer Head}${infer Tail}`
? Tail extends Uncapitalize<Tail>
? `${Lowercase<Head>}${CamelToKebab<Tail>}`
: `${Lowercase<Head>}-${CamelToKebab<Tail>}`
: S;
type K1 = CamelToKebab<"backgroundColor">; // "background-color"
type K2 = CamelToKebab<"fontSize">; // "font-size"
// 短横线转驼峰
type KebabToCamel<S extends string> =
S extends `${infer Head}-${infer Tail}`
? `${Head}${Capitalize<KebabToCamel<Tail>>}`
: S;
type C1 = KebabToCamel<"background-color">; // "backgroundColor"
8.6 类型安全的 builder 模式
interface BuilderState {
host?: string;
port?: number;
database?: string;
ssl?: boolean;
}
class DatabaseBuilder<T extends BuilderState = {}> {
private state: Partial<BuilderState> = {};
setHost(host: string): DatabaseBuilder<T & { host: string }> {
this.state.host = host;
return this as any;
}
setPort(port: number): DatabaseBuilder<T & { port: number }> {
this.state.port = port;
return this as any;
}
setDatabase(db: string): DatabaseBuilder<T & { database: string }> {
this.state.database = db;
return this as any;
}
setSSL(ssl: boolean): DatabaseBuilder<T & { ssl: boolean }> {
this.state.ssl = ssl;
return this as any;
}
build(
this: DatabaseBuilder<Required<BuilderState>>
): BuilderState {
return this.state as BuilderState;
}
}
// 使用
const builder = new DatabaseBuilder();
// ❌ 编译错误:缺少必需字段
// builder.build();
// ✅ 所有字段都设置后才能 build
const config = builder
.setHost("localhost")
.setPort(5432)
.setDatabase("mydb")
.setSSL(true)
.build();
9. 与 React 集成
9.1 组件 Props 类型
import React, { useState, useEffect, useCallback } from "react";
// 基础 Props 定义
interface ButtonProps {
variant?: "primary" | "secondary" | "danger";
size?: "sm" | "md" | "lg";
disabled?: boolean;
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
children: React.ReactNode;
}
const Button: React.FC<ButtonProps> = ({
variant = "primary",
size = "md",
disabled = false,
onClick,
children,
}) => {
return (
<button
className={`btn btn-${variant} btn-${size}`}
disabled={disabled}
onClick={onClick}
>
{children}
</button>
);
};
9.2 泛型组件
// 类型安全的列表组件
interface ListProps<T> {
items: T[];
renderItem: (item: T, index: number) => React.ReactNode;
keyExtractor: (item: T) => string;
emptyMessage?: string;
}
function List<T>({
items,
renderItem,
keyExtractor,
emptyMessage = "暂无数据",
}: ListProps<T>) {
if (items.length === 0) {
return <div className="empty">{emptyMessage}</div>;
}
return (
<ul>
{items.map((item, index) => (
<li key={keyExtractor(item)}>{renderItem(item, index)}</li>
))}
</ul>
);
}
// 使用
interface User {
id: number;
name: string;
email: string;
}
function UserList({ users }: { users: User[] }) {
return (
<List
items={users}
keyExtractor={(user) => String(user.id)}
renderItem={(user) => (
<div>
<strong>{user.name}</strong>
<span>{user.email}</span>
</div>
)}
emptyMessage="没有用户"
/>
);
}
9.3 Hooks 类型
// 自定义 Hook 的类型
interface UseFetchResult<T> {
data: T | null;
loading: boolean;
error: Error | null;
refetch: () => Promise<void>;
}
function useFetch<T>(url: string): UseFetchResult<T> {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
const fetchData = useCallback(async () => {
setLoading(true);
setError(null);
try {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const json = await response.json();
setData(json);
} catch (err) {
setError(err instanceof Error ? err : new Error(String(err)));
} finally {
setLoading(false);
}
}, [url]);
useEffect(() => {
fetchData();
}, [fetchData]);
return { data, loading, error, refetch: fetchData };
}
// 类型安全的状态管理 Hook
function useReducerTyped<S, A extends { type: string }>(
reducer: (state: S, action: A) => S,
initialState: S
): [S, React.Dispatch<A>] {
return React.useReducer(reducer, initialState);
}
9.4 事件处理类型
// 常见事件类型
interface FormProps {
onSubmit: (data: FormData) => void;
}
interface FormData {
username: string;
email: string;
message: string;
}
const ContactForm: React.FC<FormProps> = ({ onSubmit }) => {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
onSubmit({
username: formData.get("username") as string,
email: formData.get("email") as string,
message: formData.get("message") as string,
});
};
const handleInputChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => {
console.log(e.target.name, e.target.value);
};
return (
<form onSubmit={handleSubmit}>
<input name="username" onChange={handleInputChange} />
<input name="email" type="email" onChange={handleInputChange} />
<textarea name="message" onChange={handleInputChange} />
<button type="submit">发送</button>
</form>
);
};
10. 与 Vue 集成
10.1 Vue 3 组合式 API 类型
import { ref, computed, reactive, watch, onMounted } from "vue";
// ref 类型推断
const count = ref(0); // Ref<number>
const name = ref<string | null>(null); // Ref<string | null>
// reactive 类型
interface UserProfile {
id: number;
name: string;
preferences: {
theme: "light" | "dark";
language: string;
};
}
const profile = reactive<UserProfile>({
id: 1,
name: "Alice",
preferences: {
theme: "light",
language: "zh-CN",
},
});
// computed 类型
const displayName = computed<string>(() => {
return profile.name.toUpperCase();
});
// watch 类型
watch(
() => profile.name,
(newName: string, oldName: string) => {
console.log(`名字从 ${oldName} 变为 ${newName}`);
}
);
10.2 Props 与 Emits 类型
import { defineComponent, PropType } from "vue";
// 使用 defineComponent 的类型推断
const UserCard = defineComponent({
props: {
user: {
type: Object as PropType<{
id: number;
name: string;
avatar?: string;
}>,
required: true,
},
showEmail: {
type: Boolean,
default: false,
},
tags: {
type: Array as PropType<string[]>,
default: () => [],
},
},
emits: {
// 带类型验证的事件
"update:user": (user: { id: number; name: string }) => true,
delete: (id: number) => true,
},
setup(props, { emit }) {
// props.user 自动推断为正确的类型
console.log(props.user.name);
const handleDelete = () => {
emit("delete", props.user.id);
};
return { handleDelete };
},
});
10.3 <script setup> 类型
<script setup lang="ts">
import { ref, computed } from "vue";
// Props 类型定义
interface Props {
title: string;
items: Array<{
id: number;
label: string;
done: boolean;
}>;
maxItems?: number;
}
const props = withDefaults(defineProps<Props>(), {
maxItems: 100,
});
// Emits 类型定义
const emit = defineEmits<{
"update:title": [value: string];
"item-click": [id: number];
delete: [id: number];
}>();
// 响应式数据
const filter = ref<"all" | "done" | "pending">("all");
const filteredItems = computed(() => {
switch (filter.value) {
case "done":
return props.items.filter((item) => item.done);
case "pending":
return props.items.filter((item) => !item.done);
default:
return props.items;
}
});
// 暴露给父组件
defineExpose({
reset: () => {
filter.value = "all";
},
});
</script>
10.4 Provide/Inject 类型
import { provide, inject, InjectionKey, Ref } from "vue";
// 定义 InjectionKey
interface AppContext {
theme: Ref<"light" | "dark">;
locale: Ref<string>;
toggleTheme: () => void;
}
const AppContextKey: InjectionKey<AppContext> = Symbol("AppContext");
// Provider 组件
function setupApp() {
const theme = ref<"light" | "dark">("light");
const locale = ref("zh-CN");
const toggleTheme = () => {
theme.value = theme.value === "light" ? "dark" : "light";
};
provide(AppContextKey, { theme, locale, toggleTheme });
}
// Consumer 组件
function useAppContext(): AppContext {
const context = inject(AppContextKey);
if (!context) {
throw new Error("useAppContext 必须在 App 组件内使用");
}
return context;
}
11. 实战项目:类型安全的 API 客户端库
在这一节中,我们将综合运用前面学到的所有高级类型特性,构建一个生产级别的类型安全 API 客户端库。
11.1 项目结构
typed-api-client/
├── src/
│ ├── types.ts # 核心类型定义
│ ├── client.ts # 客户端实现
│ ├── interceptors.ts # 拦截器
│ ├── middleware.ts # 中间件
│ └── index.ts # 入口文件
├── tsconfig.json
└── package.json
11.2 核心类型定义
// src/types.ts
// HTTP 方法
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
// 基础响应类型
interface ApiResponse<T = unknown> {
data: T;
status: number;
statusText: string;
headers: Record<string, string>;
}
// API 错误类型
interface ApiError {
status: number;
message: string;
code?: string;
details?: Record<string, unknown>;
}
// 请求配置
interface RequestConfig {
baseURL?: string;
headers?: Record<string, string>;
timeout?: number;
params?: Record<string, string | number>;
signal?: AbortSignal;
}
// 路径参数提取
type ExtractPathParams<T extends string> =
T extends `${string}:${infer Param}/${infer Rest}`
? { [K in Param]: string } & ExtractPathParams<Rest>
: T extends `${string}:${infer Param}`
? { [K in Param]: string }
: {};
// API 端点定义
interface EndpointDef<
TMethod extends HttpMethod = HttpMethod,
TPath extends string = string,
TBody = unknown,
TResponse = unknown,
TQuery = unknown,
> {
method: TMethod;
path: TPath;
body?: TBody;
response: TResponse;
query?: TQuery;
}
// API 路由表类型
type ApiRoutes = {
[key: string]: EndpointDef;
};
// 从路由表推断请求参数
type InferRequestParams<T extends EndpointDef> =
(T["body"] extends undefined | void ? {} : { body: T["body"] }) &
(T["query"] extends undefined | void ? {} : { query: T["query"] }) &
(ExtractPathParams<T["path"]> extends Record<string, never>
? {}
: { params: ExtractPathParams<T["path"]> });
// 客户端接口
type TypedApiClient<T extends ApiRoutes> = {
[K in keyof T]: (
...args: InferRequestParams<T[K]> extends Record<string, never>
? [config?: RequestConfig]
: [params: InferRequestParams<T[K]>, config?: RequestConfig]
) => Promise<ApiResponse<T[K]["response"]>>;
};
11.3 客户端实现
// src/client.ts
interface ClientConfig {
baseURL: string;
headers?: Record<string, string>;
timeout?: number;
interceptors?: {
request?: RequestInterceptor[];
response?: ResponseInterceptor[];
};
}
type RequestInterceptor = (
config: InternalRequestConfig
) => InternalRequestConfig | Promise<InternalRequestConfig>;
type ResponseInterceptor = (
response: ApiResponse
) => ApiResponse | Promise<ApiResponse>;
interface InternalRequestConfig extends RequestConfig {
method: HttpMethod;
url: string;
body?: unknown;
}
class TypedHttpClient<T extends ApiRoutes> {
private config: ClientConfig;
private requestInterceptors: RequestInterceptor[] = [];
private responseInterceptors: ResponseInterceptor[] = [];
constructor(config: ClientConfig) {
this.config = config;
}
// 添加拦截器
addRequestInterceptor(interceptor: RequestInterceptor): void {
this.requestInterceptors.push(interceptor);
}
addResponseInterceptor(interceptor: ResponseInterceptor): void {
this.responseInterceptors.push(interceptor);
}
// 构建完整 URL
private buildUrl(
path: string,
params?: Record<string, string>,
query?: Record<string, string | number>
): string {
let url = path;
// 替换路径参数
if (params) {
for (const [key, value] of Object.entries(params)) {
url = url.replace(`:${key}`, encodeURIComponent(value));
}
}
// 添加查询参数
if (query) {
const searchParams = new URLSearchParams();
for (const [key, value] of Object.entries(query)) {
searchParams.append(key, String(value));
}
url += `?${searchParams.toString()}`;
}
return `${this.config.baseURL}${url}`;
}
// 执行请求
async request<K extends keyof T>(
route: T[K],
params?: Record<string, string>,
query?: Record<string, string | number>,
body?: unknown,
extraConfig?: RequestConfig
): Promise<ApiResponse<T[K]["response"]>> {
let config: InternalRequestConfig = {
method: route.method,
url: this.buildUrl(route.path, params, query),
headers: {
"Content-Type": "application/json",
...this.config.headers,
...extraConfig?.headers,
},
timeout: extraConfig?.timeout ?? this.config.timeout,
body,
...extraConfig,
};
// 执行请求拦截器
for (const interceptor of this.requestInterceptors) {
config = await interceptor(config);
}
// 发起请求
const response = await this.fetchWithTimeout(config);
// 执行响应拦截器
let result: ApiResponse = {
data: await response.json(),
status: response.status,
statusText: response.statusText,
headers: Object.fromEntries(response.headers.entries()),
};
for (const interceptor of this.responseInterceptors) {
result = await interceptor(result);
}
if (!response.ok) {
throw {
status: response.status,
message: result.statusText,
details: result.data,
} as ApiError;
}
return result as ApiResponse<T[K]["response"]>;
}
// 带超时的 fetch
private async fetchWithTimeout(
config: InternalRequestConfig
): Promise<Response> {
const controller = new AbortController();
const timeoutId = config.timeout
? setTimeout(() => controller.abort(), config.timeout)
: undefined;
try {
const response = await fetch(config.url, {
method: config.method,
headers: config.headers,
body: config.body ? JSON.stringify(config.body) : undefined,
signal: config.signal ?? controller.signal,
});
return response;
} finally {
if (timeoutId) clearTimeout(timeoutId);
}
}
}
// 创建类型安全的客户端工厂函数
function createApiClient<T extends ApiRoutes>(
config: ClientConfig
): TypedApiClient<T> {
const client = new TypedHttpClient<T>(config);
return new Proxy({} as TypedApiClient<T>, {
get(_target, prop: string) {
const method = prop.toUpperCase() as HttpMethod;
return (params?: any, config?: RequestConfig) => {
return client.request(
{ method, path: prop, response: null } as any,
params?.params,
params?.query,
params?.body,
config
);
};
},
});
}
11.4 定义 API 路由并使用
// src/example.ts
// 1. 定义 API 路由表
interface MyApiRoutes extends ApiRoutes {
getUser: EndpointDef<
"GET",
"/users/:userId",
undefined,
{ id: number; name: string; email: string }
>;
createUser: EndpointDef<
"POST",
"/users",
{ name: string; email: string },
{ id: number; name: string; email: string }
>;
updateUser: EndpointDef<
"PUT",
"/users/:userId",
{ name?: string; email?: string },
{ id: number; name: string; email: string }
>;
deleteUser: EndpointDef<
"DELETE",
"/users/:userId",
undefined,
{ success: boolean }
>;
listPosts: EndpointDef<
"GET",
"/posts",
undefined,
Array<{ id: number; title: string; body: string }>,
{ page?: number; limit?: number }
>;
getPostComments: EndpointDef<
"GET",
"/posts/:postId/comments",
undefined,
Array<{ id: number; body: string }>
>;
}
// 2. 创建客户端实例
const api = createApiClient<MyApiRoutes>({
baseURL: "https://api.example.com",
timeout: 5000,
headers: {
Authorization: "Bearer your-token",
},
});
// 3. 类型安全的 API 调用
// ✅ 正确使用 - 完整的类型提示和检查
const user = await api.getUser({
params: { userId: "123" },
});
// user.data 的类型为 { id: number; name: string; email: string }
const newUser = await api.createUser({
body: { name: "Alice", email: "alice@example.com" },
});
const posts = await api.listPosts({
query: { page: 1, limit: 10 },
});
const comments = await api.getPostComments({
params: { postId: "42" },
});
// ❌ 编译错误示例
// api.getUser({}); // 缺少 params.userId
// api.createUser({ body: { name: 123 } }); // body 类型不匹配
// api.listPosts({ query: { page: "1" } }); // page 应为 number
11.5 拦截器使用
// 添加请求拦截器 - 自动附加 token
api.addRequestInterceptor((config) => {
const token = localStorage.getItem("auth_token");
if (token) {
config.headers = {
...config.headers,
Authorization: `Bearer ${token}`,
};
}
return config;
});
// 添加响应拦截器 - 统一错误处理
api.addResponseInterceptor((response) => {
if (response.status === 401) {
// Token 过期,跳转登录
window.location.href = "/login";
}
return response;
});
// 添加响应拦截器 - 日志记录
api.addResponseInterceptor((response) => {
console.log(
`[API] ${response.status} ${response.statusText}`,
response.data
);
return response;
});
11.6 错误处理
// 类型安全的错误处理
async function safeApiCall<T>(
fn: () => Promise<ApiResponse<T>>
): Promise<{ data: T; error: null } | { data: null; error: ApiError }> {
try {
const response = await fn();
return { data: response.data, error: null };
} catch (error) {
const apiError: ApiError =
error && typeof error === "object" && "status" in error
? (error as ApiError)
: { status: 0, message: String(error) };
return { data: null, error: apiError };
}
}
// 使用
const result = await safeApiCall(() =>
api.getUser({ params: { userId: "123" } })
);
if (result.error) {
console.error(`请求失败: ${result.error.message}`);
} else {
console.log(`用户名: ${result.data.name}`);
}
11.7 完整项目总结
这个实战项目综合运用了以下 TypeScript 高级特性:
| 特性 | 应用场景 |
|---|---|
| 泛型 | 路由表类型参数化、响应类型推断 |
| 条件类型 | InferRequestParams 根据端点定义推断参数 |
| 映射类型 | TypedApiClient 从路由表生成方法签名 |
| 模板字面量类型 | 路径参数提取 ExtractPathParams |
| infer 关键字 | 提取路径中的参数名 |
| 交叉类型 | 组合多种请求参数类型 |
| 索引访问类型 | 访问嵌套类型定义 |
| Proxy | 运行时动态生成方法 |
通过这些特性,我们实现了一个零样板代码的 API 客户端:只需定义路由表类型,所有 API 调用都自动获得完整的类型提示、参数检查和返回值类型推断。
总结
本教程涵盖了 TypeScript 类型系统的核心高级特性:
- 泛型 是构建可复用类型的基础,结合约束和默认值可以实现灵活的类型抽象。
- 条件类型 允许根据类型关系进行条件判断,配合
infer可以提取和转换类型。 - 映射类型 可以批量转换对象类型的属性,是构建工具类型的关键。
- 模板字面量类型 将字符串操作引入类型系统,适用于路由、CSS 属性等场景。
- 装饰器 提供了声明式的元编程能力,适合日志、验证、缓存等横切关注点。
- 模块系统 是大型项目组织代码的基础,理解模块解析策略至关重要。
- 声明文件 让 TypeScript 与 JavaScript 生态无缝集成。
- 类型体操 是对以上所有特性的综合运用,展示了类型系统的图灵完备性。
掌握这些高级特性,你将能够编写出真正类型安全、可维护且表现力强的 TypeScript 代码。
进阶资源推荐
- TypeScript 官方文档
- Type Challenges - 类型体操练习题库
- Total TypeScript - Matt Pocock 的 TypeScript 进阶课程