ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

claude-skills 之 TypeScript Pro:类型安全设计模式实战指南

claude-skills 之 TypeScript Pro:类型安全设计模式实战指南 claude-skills 之 TypeScript Pro类型安全设计模式实战指南【免费下载链接】claude-skills67 Specialized Skills for Full-Stack Developers. Transform Claude Code into your expert pair programmer.项目地址: https://gitcode.com/GitHub_Trending/claud/claude-skills本文以 claude-skills 仓库中 TypeScript Pro 技能 的核心参考文档 patterns.md 为主线系统讲解 8 种以类型安全为首要目标的 TypeScript 设计模式实现方案。文中所有代码均可在项目仓库的 patterns.md 中查到原文并结合 advanced-types.md、type-guards.md、utility-types.md 进行原理性解读。读完本文你将掌握如何用 TypeScript 的类型系统把运行时报错提前到编译时报错并能直接落地到自己的业务代码中。应用场景该技能用于需要高级泛型、条件类型、映射类型、可辨识联合的 TypeScript 项目开发。按 SKILL.md 的定义其典型工作流是分析类型架构 → 设计类型优先的 API → 用类型守卫与联合类型实现 → 优化构建 → 用tsc --noEmit与 type-coverage 验证。本文的每个模式都服务于这条工作流的第二步与第三步。一、模式设计的总原则类型优先在展开具体模式之前先明确 TypeScript Pro 技能对编码风格的强制约束见 SKILL.md 的 Constraints 章节必须做开启严格模式及全部编译标志采用类型优先的 API 设计为领域建模实现 branded types名义类型用satisfies操作符做类型校验为状态机创建可辨识联合为库生成声明文件针对类型推断做优化。禁止做未经说明使用显式any跳过公共 API 的类型覆盖混用类型导入与值导入关闭严格空检查无必要地使用as断言忽略编译器性能警告跳过声明文件生成使用enum优先使用带as const的常量对象。patterns.md 中全部代码示例都严格遵循上述约束不出现无理由的any、不引入enum、尽可能用类型系统表达业务不变量。下面逐模式展开。二、Builder 模式从流畅 API到编译期校验1. 基础版类型安全的链式构建Builder 模式用于分步构造复杂对象。基础实现用this返回类型保证链式调用的流畅性class UserBuilder { private data: PartialUser {}; setName(name: string): this { this.data.name name; return this; } setEmail(email: string): this { this.data.email email; return this; } setAge(age: number): this { this.data.age age; return this; } build(): User { if (!this.data.name || !this.data.email) { throw new Error(Name and email are required); } return this.data as User; } } // Fluent API with type safety const user new UserBuilder() .setName(John) .setEmail(johnexample.com) .setAge(30) .build();要点分析setXxx方法返回this子类继承时依然能保持具体类型这正是返回this而非UserBuilder的原因。构建期校验放在build()中统一收口必填字段缺失时抛错避免构建一半的脏对象在系统中流转。PartialUser作为内部可变缓冲区最后通过as User收窄——这是文档中少数允许的类型断言因为它建立在build()的运行时校验之上属于用断言换取收敛的受控用法。2. 进阶版用条件类型与映射类型做编译期校验更高级的做法是让 TypeScript 在编译期就知道哪些字段已设置从而在build()被调用时给出类型错误而非运行时错误。这需要用到映射类型、键重映射与模板字面量类型详见 advanced-types.mdtype BuilderT, K extends keyof T never { [P in keyof T as set${Capitalizestring P}]: ( value: T[P] ) BuilderT, K | P; } { build: K extends keyof T ? () T : never; }; function createBuilderT(): BuilderT { const data {} as T; return new Proxy({} as BuilderT, { get(_, prop: string) { if (prop build) { return () data; } if (prop.startsWith(set)) { const key prop.slice(3).toLowerCase(); return (value: any) { (data as any)[key] value; return this; }; } } }); }原理拆解对应 advanced-types.md 中的相关模式as \set${Capitalizestring P}键重映射把name属性映射成setName方法名。string P收窄P到字符串以便Capitalize 工作。K extends keyof T作为类型累积器每调用一次setXxxK就并入该字段返回的BuilderT, K | P会记住已经设置的字段。build: K extends keyof T ? () T : never条件类型——只有当K覆盖了T的全部键时build才可调用否则build的类型是never编译期直接报build 不存在。运行时用Proxy动态生成set方法把属性写入闭包中的data对象。这段代码本质上是把状态推进记录在类型参数K中的元编程技巧属于 type-level programming 的典型应用。它与 SKILL.md 推荐的 tsconfig 中exactOptionalPropertyTypes、noUncheckedIndexedAccess等严格选项配合时能进一步消除any相关隐患。三、Factory 模式工厂方法与依赖注入容器1. 抽象工厂用条件类型约束配置参数工厂模式用于不直接指定具体类地创建对象。文档用 Logger 示例展示了如何在工厂中同时保证返回类型与配置参数的精确性interface Logger { log(message: string): void; } class ConsoleLogger implements Logger { log(message: string): void { console.log(message); } } class FileLogger implements Logger { constructor(private filename: string) {} log(message: string): void { // Write to file } } type LoggerType console | file; type LoggerConfigT extends LoggerType T extends file ? { type: T; filename: string } : { type: T }; class LoggerFactory { static createT extends LoggerType(config: LoggerConfigT): Logger { switch (config.type) { case console: return new ConsoleLogger(); case file: return new FileLogger(config.filename); default: throw new Error(Unknown logger type); } } } const consoleLogger LoggerFactory.create({ type: console }); const fileLogger LoggerFactory.create({ type: file, filename: app.log });关键点LoggerConfigT是条件类型见 advanced-types.md 的 Conditional Types 一节当type: file时必须传filename当type: console时禁止传filename。这实现了配置按类型自动收窄。调用方不感知具体实现类只依赖Logger接口符合依赖倒置原则。switch中的default分支保证了枚举扩展时的兜底处理配合never检查可形成穷尽校验见 type-guards.md 的assertNever模式。2. 泛型容器极简依赖注入同一节还给出了一个基于构造器令牌的依赖注入容器type ConstructorT new (...args: any[]) T; class Container { private instances new MapConstructorany, any(); registerT(token: ConstructorT, instance: T): void { this.instances.set(token, instance); } resolveT(token: ConstructorT): T { const instance this.instances.get(token); if (!instance) { throw new Error(No instance registered for ${token.name}); } return instance; } }ConstructorT把构造器类型抽象为一等类型resolve返回的实例类型与注册令牌绑定编译期即可验证解析结果。注册与解析都围绕ConstructorT令牌消除了字符串键带来的类型丢失问题。四、Repository 模式泛型 CRUD 与类型安全的查询构建器Repository 模式将数据访问层抽象成接口使业务代码不依赖具体数据库。文档先用泛型接口定义契约再用UserRepository实现interface Entity { id: string | number; } interface RepositoryT extends Entity { find(id: T[id]): PromiseT | null; findAll(): PromiseT[]; create(data: OmitT, id): PromiseT; update(id: T[id], data: PartialOmitT, id): PromiseT; delete(id: T[id]): Promisevoid; } class UserRepository implements RepositoryUser { async find(id: User[id]): PromiseUser | null { // Database query return null; } async findAll(): PromiseUser[] { return []; } async create(data: OmitUser, id): PromiseUser { // Insert into database return { id: 1, ...data }; } async update(id: User[id], data: PartialOmitUser, id): PromiseUser { // Update database return { id, name: , email: , ...data }; } async delete(id: User[id]): Promisevoid { // Delete from database } }设计要点id: T[id]使用索引访问类型indexed access保证find参数与实体主键类型严格一致。OmitT, id用于create新增时不允许调用方传入主键。PartialOmitT, id用于update只传需要修改的字段这是内置工具类型Partial、Omit的组合用法详见 utility-types.md。紧随其后的是一个内存版类型安全查询构建器class QueryBuilderT { private conditions: Array(item: T) boolean []; whereK extends keyof T(key: K, value: T[K]): this { this.conditions.push(item item[key] value); return this; } execute(items: T[]): T[] { return items.filter(item this.conditions.every(condition condition(item)) ); } } const query new QueryBuilderUser() .where(email, johnexample.com) .where(age, 30);whereK extends keyof T(key: K, value: T[K])通过keyof T约束键名通过T[K]约束值类型杜绝把字符串传给 number 字段这类低级错误。条件以(item: T) boolean谓词数组累积execute时用every全部满足才放行——这与 type-guards.md 中数组方法内的类型守卫思路一脉相承。五、类型安全的 REST API 客户端模板字面量与 infer 的集大成这是 patterns.md 中最具类型系统含量的一节。目标是让client.request(GET, /users/:id, ...)的参数与返回值在编译期就被完全推断。type HttpMethod GET | POST | PUT | DELETE | PATCH; type ApiEndpoints { /users: { GET: { response: User[] }; POST: { body: CreateUserDto; response: User }; }; /users/:id: { GET: { params: { id: string }; response: User }; PUT: { params: { id: string }; body: UpdateUserDto; response: User }; DELETE: { params: { id: string }; response: void }; }; /posts: { GET: { query: { userId?: string }; response: Post[] }; POST: { body: CreatePostDto; response: Post }; }; }; type ExtractParamsT extends string T extends ${infer _Start}/:${infer Param}/${infer Rest} ? { [K in Param]: string } ExtractParams/${Rest} : T extends ${infer _Start}/:${infer Param} ? { [K in Param]: string } : {}; class ApiClient { async request Path extends keyof ApiEndpoints, Method extends keyof ApiEndpoints[Path] ( method: Method, path: Path, options?: ApiEndpoints[Path][Method] extends { body: infer B } ? { body: B } : ApiEndpoints[Path][Method] extends { params: infer P } ? { params: P } : ApiEndpoints[Path][Method] extends { query: infer Q } ? { query: Q } : never ): Promise ApiEndpoints[Path][Method] extends { response: infer R } ? R : never { // Make HTTP request return null as any; } } const client new ApiClient(); // Type-safe API calls const users await client.request(GET, /users); const user await client.request(GET, /users/:id, { params: { id: 1 } }); const newUser await client.request(POST, /users, { body: { name: John, email: johnexample.com } });类型机制逐层拆解端点即类型字典ApiEndpoints用普通对象类型描述全部 API路径作为键每个键下按 HTTP 方法描述请求与响应。改后端字段只需要改这一处字典全项目调用点自动同步。ExtractParams用模板字面量与infer提取路径参数\${infer _Start}/:${infer Param}/${infer Rest}从路径字符串中拆出:param片段并映射为对象类型此技巧在 [advanced-types.md](https://link.gitcode.com/i/1c8cac39abae1f52f87f55989bc543ec) 的ExtractRouteParams中有同源实现。递归处理剩余片段最终得到{ id: string }。infer B / infer P / infer Q分层推断 options三级条件类型按body、params、query顺序探测端点类型中存在的字段决定第三个参数应该携带什么都没有时收窄为never意味着不能传 options。返回值由infer R推断返回response字段对应的类型const users直接获得User[]。这就是文档所说的REST API client with type safety——一个典型 endpoint 路由把 advanced-types.md 中的模板字面量类型、条件类型、infer、索引访问全部串联了起来。六、State Machine 模式可辨识联合 映射类型驱动转移表状态机把对象的行为建模为状态 × 事件 → 新状态的转移表。TypeScript 的映射类型与可辨识联合非常适合表达这一结构type State idle | loading | success | error; type Event | { type: FETCH } | { type: SUCCESS; data: any } | { type: ERROR; error: Error } | { type: RETRY }; type StateMachine { [S in State]: { [E in Event[type]]?: State; }; }; const machine: StateMachine { idle: { FETCH: loading }, loading: { SUCCESS: success, ERROR: error }, success: { FETCH: loading }, error: { RETRY: loading } }; class StateManagerS extends string, E extends { type: string } { constructor( private state: S, private transitions: RecordS, PartialRecordE[type], S ) {} getState(): S { return this.state; } dispatch(event: E): S { const nextState this.transitions[this.state][event.type]; if (nextState undefined) { throw new Error(Invalid transition from ${this.state} on ${event.type}); } this.state nextState; return this.state; } } const manager new StateManagerState, Event(idle, machine); manager.dispatch({ type: FETCH }); // loading manager.dispatch({ type: SUCCESS, data: {} }); // success转移表由映射类型生成StateMachine用[S in State]遍历所有状态每个状态下的键为Event[type]的联合值为目标状态或undefined。未被允许的转移在编译期就不存在。可辨识联合Event是典型的 discriminated uniondispatch(event: E)的event.type即判别字段与 type-guards.md 中Shape的kind判别字段一脉相承。运行时兜底nextState undefined时抛非法转移错误。Event用可辨识联合后可在SUCCESS分支安全访问data、在ERROR分支安全访问error。该模式与 SKILL.md 中为状态机创建可辨识联合的 MUST DO 直接对应也是 type-guards.md 中LoadingState / SuccessState / ErrorState示例的工程化延伸。七、Decorator 模式日志与记忆化的方法装饰装饰器用于在不侵入方法内部的前提下横切添加行为。文档给出Log与Memoize两个方法装饰器function Log( target: any, propertyKey: string, descriptor: PropertyDescriptor ) { const originalMethod descriptor.value; descriptor.value function (...args: any[]) { console.log(Calling ${propertyKey} with, args); const result originalMethod.apply(this, args); console.log(Result:, result); return result; }; return descriptor; } function Memoize( target: any, propertyKey: string, descriptor: PropertyDescriptor ) { const originalMethod descriptor.value; const cache new Mapstring, any(); descriptor.value function (...args: any[]) { const key JSON.stringify(args); if (cache.has(key)) { return cache.get(key); } const result originalMethod.apply(this, args); cache.set(key, result); return result; }; return descriptor; } class Calculator { Log Memoize fibonacci(n: number): number { if (n 1) return n; return this.fibonacci(n - 1) this.fibonacci(n - 2); } }Log包装原方法调用前后分别打印参数与结果方便排查。Memoize用Mapstring, any以参数序列化结果为键缓存返回值对fibonacci这类指数级递归非常有效注意装饰器以descriptor.value包装方法内部的递归调用this.fibonacci也会命中同一缓存包装。装饰器函数三参数签名target、propertyKey、descriptor是 TC39 装饰器提案在 TypeScript 下的传统写法。若你的 tsconfig 开启了experimentalDecorators可直接照搬若使用 5.0 标准装饰器语法签名形式略有差异使用时需按项目 tsconfig 选择tsconfig 选项见 configuration.md。八、Result/Either 模式类型安全的错误处理成功与失败作为一等类型显式建模是消除隐式异常依赖的主流做法。文档先给出判别联合版本的Resulttype ResultT, E Error | { success: true; value: T } | { success: false; error: E }; function okT(value: T): ResultT, never { return { success: true, value }; } function errE(error: E): Resultnever, E { return { success: false, error }; } async function fetchUser(id: string): PromiseResultUser, string { try { const response await fetch(/api/users/${id}); if (!response.ok) { return err(User not found); } const user await response.json(); return ok(user); } catch (error) { return err(Network error); } } // Usage with pattern matching const result await fetchUser(123); if (result.success) { console.log(result.value.name); // Type-safe access } else { console.error(result.error); // Type-safe error }ok返回ResultT, never、err返回Resultnever, E用never占据另一侧让类型系统能精确表达这一侧不存在。消费端通过if (result.success)分支收窄即 discriminated union 的可辨识字段收窄value与error在各自分支中都获得完整类型信息。接着是函子风格的Either单子class EitherL, R { private constructor( private readonly value: L | R, private readonly isRight: boolean ) {} static leftL, R(value: L): EitherL, R { return new EitherL, R(value, false); } static rightL, R(value: R): EitherL, R { return new EitherL, R(value, true); } mapT(fn: (value: R) T): EitherL, T { if (this.isRight) { return Either.right(fn(this.value as R)); } return Either.left(this.value as L); } flatMapT(fn: (value: R) EitherL, T): EitherL, T { if (this.isRight) { return fn(this.value as R); } return Either.left(this.value as L); } getOrElse(defaultValue: R): R { return this.isRight ? (this.value as R) : defaultValue; } }惯例上Right表示成功、Left表示失败。map只在成功路径上变换值失败原样透传flatMap支持链式组合多个可能失败的步骤getOrElse提供兜底值。构造函数私有化强制只能通过Either.left / Either.right创建避免非法状态。该模式与 type-guards.md 中ResultT, E判别联合status: success | error | loading的三种状态建模互为补充可按场景选用。九、Singleton 模式单例类与泛型单例工厂单例保证一个类只有一个实例。文档给出经典实现与泛型工厂两种形态class Database { private static instance: Database; private constructor() { // Private constructor prevents instantiation } static getInstance(): Database { if (!Database.instance) { Database.instance new Database(); } return Database.instance; } queryT(sql: string): PromiseT[] { // Execute query return Promise.resolve([]); } } const db Database.getInstance();私有的构造函数从语言层面禁止new Database()唯一入口是getInstance()天然防止每个模块各建一个连接的重复实例问题。泛型版本则把单例化抽象成可复用的高阶函数function singletonT(factory: () T): () T { let instance: T | undefined; return () { if (!instance) { instance factory(); } return instance; }; } const getConfig singleton(() ({ apiUrl: process.env.API_URL, apiKey: process.env.API_KEY }));singleton接收工厂函数返回一个首次调用时惰性创建、之后缓存的访问函数类型参数T从工厂返回类型自动推断。它把单例从类解耦为函数的记忆化适用于配置、连接池、服务客户端等任意共享资源且不需要改造成类。十、Quick Reference模式速查表patterns.md 在文末给出速查表完整继承如下PatternUse CaseBuilderConstruct complex objects step by step分步构造复杂对象FactoryCreate objects without specifying exact class不指定具体类地创建对象RepositoryAbstract data access layer抽象数据访问层API ClientType-safe HTTP requests类型安全的 HTTP 请求State MachineManage state transitions管理状态转移DecoratorAdd behavior to methods为方法添加横切行为Result/EitherType-safe error handling类型安全的错误处理SingletonEnsure single instance保证单实例Query BuilderType-safe database queries类型安全的数据库查询ContainerDependency injection依赖注入十一、落地清单把这套模式接到你的项目开启严格配置参考 SKILL.md 中推荐的 tsconfig 组合strict、noUncheckedIndexedAccess、noImplicitOverride、exactOptionalPropertyTypes、isolatedModules、incremental等完整选项说明见 configuration.md先让编译器替你兜底。先建类型字典对涉及 API 的项目先按第五节的方式建立ApiEndpoints类型字典对数据层项目先按第四节定义RepositoryT extends Entity接口。错误显式化把 try/catch 替换为Result/Either返回值配合 type-guards.md 的assertNever穷尽检查让每一条失败路径都在类型层面被覆盖。状态机建模用可辨识联合描述状态与事件用映射类型生成转移表杜绝非法转移在运行时才暴露。验证按 SKILL.md 的核心工作流每次改动后运行tsc --noEmit确保零错误再用type-coverage检查公共 API 的类型覆盖率对库项目启用declaration与declarationMap生成.d.ts。以上全部示例与原理均出自仓库 TypeScript Pro 技能的 patterns.md 及其配套参考文档可直接对照原文按需取用。【免费下载链接】claude-skills67 Specialized Skills for Full-Stack Developers. Transform Claude Code into your expert pair programmer.项目地址: https://gitcode.com/GitHub_Trending/claud/claude-skills创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表