TypeScript的泛型系统不仅限于Array<T>这类基础用法。在实际业务开发中,泛型约束、条件类型和映射类型组合使用,能够实现编译期的类型安全校验,减少运行时错误。本文通过表单验证、API请求封装和状态管理三个实测场景,讲解TypeScript高级类型技巧。
泛型约束:API请求响应类型安全封装
封装HTTP请求时,不同接口返回的数据结构不同。通过泛型约束实现调用方自动推导返回类型:
// 定义API响应基础结构
interface ApiResponse<T> {
code: number;
message: string;
data: T;
}
// 业务数据类型
interface User {
id: number;
name: string;
email: string;
}
interface Product {
id: number;
name: string;
price: number;
}
// 泛型请求函数
async function request<T>(
url: string,
options?: RequestInit
): Promise<ApiResponse<T>> {
const response = await fetch(url, options);
const result: ApiResponse<T> = await response.json();
if (result.code !== 0) {
throw new Error(result.message);
}
return result;
}
// 调用时自动推导返回类型
const userRes = await request<User>('/api/user/1');
userRes.data.name; // string类型,编译器自动推导
userRes.data.price; // 编译错误:User上不存在price属性
const productRes = await request<Product>('/api/product/1');
productRes.data.price; // number类型
泛型<T>在函数调用时由参数显式传入,编译器据此推导data字段的具体类型,访问不存在的属性会在编译期报错。
条件类型:动态表单字段类型推导
后台管理系统中,表单字段配置驱动渲染。不同字段类型对应不同的验证规则和值类型。使用条件类型实现字段配置到表单值的类型映射:
// 字段类型定义
type FieldType = 'text' | 'number' | 'select' | 'date' | 'switch';
// 条件类型:根据字段类型推导值类型
type FieldValue<T extends FieldType> =
T extends 'text' ? string :
T extends 'number' ? number :
T extends 'select' ? string :
T extends 'date' ? string : // ISO日期字符串
T extends 'switch' ? boolean :
never;
// 字段配置类型
interface FieldConfig<T extends FieldType> {
name: string;
label: string;
type: T;
required: boolean;
options?: { label: string; value: string }[]; // select类型专用
rules?: (value: FieldValue<T>) => string | true;
}
// 表单值类型:自动收集所有字段值
type FormValues<C extends FieldConfig<any>[]> = {
[K in C[number]['name']]:
C[number] extends FieldConfig<infer F>
? FieldValue<F>
: never
};
// 实际使用
const fields = [
{ name: 'username', label: '用户名', type: 'text' as const, required: true },
{ name: 'age', label: '年龄', type: 'number' as const, required: false },
{ name: 'vip', label: 'VIP用户', type: 'switch' as const, required: true }
] satisfies FieldConfig<any>[];
// formValues的类型自动推导为:
// { username: string; age: number; vip: boolean }
type FormType = FormValues<typeof fields>;
const formValues: FormType = {
username: 'admin', // string
age: 25, // number
vip: true // boolean
};
// formValues.username = 123; // 编译错误:不能将number赋值给string
satisfies关键字(TypeScript 4.9+)用于校验字段配置符合FieldConfig结构,同时保留字面量类型信息。条件类型FieldValue根据字段类型映射到对应的值类型,FormValues将配置数组转换为键值对类型。
映射类型:DTO与实体类型自动转换
前后端数据交互中,后端返回snake_case,前端使用camelCase。手动维护两套类型容易遗漏。通过映射类型自动生成转换类型:
// 后端返回的DTO(snake_case)
interface UserDTO {
user_id: number;
user_name: string;
created_at: string;
is_active: boolean;
}
// snake_case -> camelCase 字符串类型转换
type SnakeToCamel<S extends string> =
S extends `${infer Head}_${infer Tail}`
? `${Head}${Capitalize<SnakeToCamel<Tail>>}`
: S;
// 映射类型:将DTO所有键转为camelCase
type CamelCase<T> = {
[K in keyof T as SnakeToCamel<string & K>]: T[K]
};
// 自动生成前端Entity类型
type UserEntity = CamelCase<UserDTO>;
// 等价于:
// {
// userId: number;
// userName: string;
// createdAt: string;
// isActive: boolean;
// }
// 通用转换函数
function toCamelCase<T extends Record<string, any>>(
dto: T
): CamelCase<T> {
const result: any = {};
for (const [key, value] of Object.entries(dto)) {
const camelKey = key.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
result[camelKey] = value;
}
return result;
}
const dto: UserDTO = { user_id: 1, user_name: 'admin', created_at: '2026-08-05', is_active: true };
const entity = toCamelCase(dto);
entity.userId; // number - 类型安全
entity.userName; // string
模板字面量类型:路由参数类型安全
Vue Router或React Router中,动态路由参数通过字符串拼接构造。模板字面量类型可在编译期校验路由格式:
// 定义路由模式
type RoutePattern =
| `/users`
| `/users/${number}`
| `/posts/${number}/comments`
| `/posts/${number}/edit`;
// 类型安全的路由生成函数
function createRoute<P extends RoutePattern>(path: P): P {
return path;
}
// 编译期校验
const r1 = createRoute('/users'); // OK
const r2 = createRoute('/users/123'); // OK
const r3 = createRoute('/posts/45/edit'); // OK
const r4 = createRoute('/users/abc'); // 编译错误:abc不是number
const r5 = createRoute('/unknown'); // 编译错误:不在RoutePattern中
类型收窄与运行时校验结合
TypeScript的类型在运行时被擦除。使用zod在运行时校验数据结构,同时自动推导TypeScript类型:
import { z } from 'zod';
// 定义Schema
const UserSchema = z.object({
id: z.number(),
name: z.string().min(1).max(50),
email: z.string().email(),
age: z.number().int().min(0).max(150).optional(),
role: z.enum(['admin', 'user', 'guest'])
});
// 自动推导类型,无需interface
type User = z.infer<typeof UserSchema>;
// 运行时校验
function parseUser(data: unknown): User {
return UserSchema.parse(data); // 校验失败抛出ZodError
}
// 安全解析
function safeParseUser(data: unknown) {
const result = UserSchema.safeParse(data);
if (result.success) {
result.data; // User类型,类型安全
return result.data;
} else {
result.error; // ZodError,包含详细错误信息
return null;
}
}
z.infer从Schema自动推导TypeScript类型,保证运行时校验规则与编译期类型定义始终一致,避免定义与校验逻辑脱节。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/typescript-fan-xing-yue-shu-yu-tiao-jian-lei-xing-shi-zhan/