TypeScript 类型系统实战:从基础到高级工具类型
前端工程
为什么工具站需要 TypeScript?
工具库有 200+ 工具页、4 语言 i18n、复杂的工具客户端配置。纯 JavaScript 在以下场景容易出错:
- 工具配置对象的字段拼写错误
- i18n key 不存在但未报错
- API 响应结构与预期不符
- 重构时遗漏更新引用
TypeScript 在编译期捕获这些错误,而非运行时。
核心类型技巧
1. 类型推断 vs 显式标注
// ✅ 让 TS 推断(简洁)
const tools = [{ href: '/pdf/merge', name: 'PDF合并' }] as const;
// ✅ 需要约束时显式标注
function getTool(href: string): ToolItem | undefined {
return ALL_TOOLS.find(t => t.href === href);
}
2. 泛型约束
function encode<T extends string | ArrayBuffer>(
input: T
): T extends string ? string : ArrayBuffer {
// 根据输入类型返回不同结果
}
3. 联合类型与类型守卫
type ProcessResult =
| { status: 'success'; data: Blob }
| { status: 'error'; message: string };
function handleResult(result: ProcessResult) {
if (result.status === 'success') {
downloadBlob(result.data); // TS 知道 data 存在
}
}
高级工具类型
Partial、Required、Pick、Omit
type ToolConfig = {
href: string;
name: string;
blurb: string;
i18nKey?: string;
};
type ToolUpdate = Partial<ToolConfig>; // 所有字段可选
type ToolRequired = Required<ToolConfig>; // 所有字段必填
type ToolPreview = Pick<ToolConfig, 'name' | 'blurb'>; // 只取部分
条件类型
type IsString<T> = T extends string ? true : false;
type A = IsString<'hello'>; // true
type B = IsString<42>; // false
映射类型
type Readonly<T> = { readonly [K in keyof T]: T[K] };
type Nullable<T> = { [K in keyof T]: T[K] | null };
JSON → TypeScript 生成
工具库 JSON 转 TypeScript 工具的实现逻辑:
function jsonToTypeScript(json: unknown, name = 'Root'): string {
if (Array.isArray(json)) {
return `${name}[]`;
}
if (typeof json === 'object' && json !== null) {
const fields = Object.entries(json).map(([key, value]) => {
const type = jsonToTypeScript(value, capitalize(key));
return ` ${key}: ${type};`;
});
return `interface ${name} {\n${fields.join('\n')}\n}`;
}
return typeof json; // string | number | boolean
}
输入 API 响应 JSON,一键生成 TypeScript interface——减少手动编写类型的工作量。
实用模式
satisfies 运算符(TS 4.9+)
const config = {
locales: ['zh-CN', 'en'],
defaultLocale: 'zh-CN',
} satisfies RoutingConfig; // 保留字面量类型,同时校验结构
const 类型参数(TS 5.0+)
function createTool<const T extends string>(href: T) {
return { href } as const;
}
const tool = createTool('/pdf/merge'); // href 类型为 '/pdf/merge',非 string
总结
TypeScript 的类型系统不仅是「加类型注解」,更是一套编译期编程语言。掌握泛型、条件类型和工具类型,可以构建自文档化、重构安全的代码库。工具库的 JSON 转 TypeScript 工具则是类型驱动开发的实用起点。
模板字面量类型(TS 4.1+)
模板字面量类型让类型系统能够操作字符串模式:
// 定义 i18n 路由类型
type Locale = 'zh-CN' | 'en' | 'ja' | 'zh-TW';
type ToolRoute = `/${Locale}/image/${string}` | `/${Locale}/pdf/${string}` | `/${Locale}/encode/${string}`;
// 所有工具路由在编译期校验
const validRoute: ToolRoute = '/en/pdf/merge'; // ✅
const badRoute: ToolRoute = '/invalid/pdf/merge'; // ❌ 类型错误
// 运行时也支持的类型级转换
type CamelCase<S extends string> =
S extends `${infer P}_${infer R}`
? `${P}${Capitalize<CamelCase<R>>}`
: S;
type Result = CamelCase<'tool_config_key'>; // "toolConfigKey"
工具库用模板字面量类型定义了所有路由参数的类型约束,使路由跳转在 IDE 中即可获得自动补全和拼写校验。
Branded Types(品牌类型)
TypeScript 默认是结构类型系统,不同语义但同结构的类型相互兼容。Branded Types 用编译期标记区分它们:
type Brand<T, B> = T & { __brand: B };
type UserId = Brand<string, 'UserId'>;
type OrderId = Brand<string, 'OrderId'>;
type FileHash = Brand<string, 'FileHash'>;
function createUserId(id: string): UserId {
return id as UserId; // 显式转换入口
}
function getUser(id: UserId) { /* ... */ }
function getOrder(id: OrderId) { /* ... */ }
const uid = createUserId('usr_123');
getUser(uid); // ✅
getOrder(uid); // ❌ 类型错误!UserId 不能作为 OrderId 使用
实用场景:
- 文件处理工具:区分
InputFilevsOutputFile - API 响应:区分
RawResponsevsValidatedResponse - 安全性:防止各种 ID 之间混用
Discriminated Unions(可辨识联合)
通过字面量 kind 字段创建穷举完备的类型判断:
// 工具处理结果的联合类型
type ToolResult =
| { kind: 'pdf_merge'; pages: number; blob: Blob }
| { kind: 'json_format'; formatted: string; errors: string[] }
| { kind: 'image_compress'; originalSize: number; compressedSize: number; ratio: number }
| { kind: 'error'; code: string; message: string };
function renderResult(result: ToolResult) {
switch (result.kind) {
case 'pdf_merge':
return `PDF 合并完成,共 ${result.pages} 页`;
case 'json_format':
return result.errors.length
? `JSON 格式化完成,发现 ${result.errors.length} 个错误`
: `JSON 格式化成功`;
case 'image_compress':
return `压缩完成:${(result.ratio * 100).toFixed(1)}% (${result.originalSize} → ${result.compressedSize})`;
case 'error':
return `处理失败:${result.message}`;
// ❌ 遗漏任何 case 会触发编译错误(noUnusedLocals + exhaustive check)
}
}
工具库的 200+ 工具全部使用 Discriminated Union 管理状态,确保每种工具状态都有对应的 UI 渲染逻辑,不可能遗漏。
Async 类型与类型级验证
结合 zod 在编译期和运行时双重验证类型安全:
import { z } from 'zod';
// 一次定义,同时获得 TS 类型 + 运行时校验
const ToolConfigSchema = z.object({
href: z.string().startsWith('/'),
name: z.string().min(1).max(50),
blurb: z.string().max(200),
category: z.enum(['PDF', 'Image', 'Encode', 'Dev', 'Utils']),
tags: z.array(z.string()).max(5),
});
type ToolConfig = z.infer<typeof ToolConfigSchema>; // 自动生成 TS 类型
// 运行时安全校验
function loadTools(json: unknown): ToolConfig[] {
return z.array(ToolConfigSchema).parse(json);
}
本站提供浏览器本地工具,免注册即可试用 →
#TypeScript#类型系统#泛型#工具类型#工程化