Co-Pilot / 辅助式
更新于 a month ago

typescript-best-practices

00xBigBoss
0.0k
0xBigBoss/claude-code/.claude/skills/typescript-best-practices
76
Agent 评分

💡 摘要

提供TypeScript最佳实践,包括类型优先开发、可区分联合类型、使用Zod进行运行时验证和函数式编程模式。

🎯 适合人群

TypeScript开发者全栈工程师代码审查员技术负责人前端开发人员

🤖 AI 吐槽:这个技能就像一个迷路的TypeScript风格指南——全是理论,没有可执行代码。

安全分析中风险

该技能提倡使用Zod模式验证,有助于防止注入攻击和数据损坏。然而,它鼓励直接使用fetch()而没有安全头或输入清理示例,并建议从环境变量加载配置,如果管理不当可能暴露密钥。缓解措施:始终验证和清理外部输入,使用HTTP安全头,并实施适当的密钥管理。


name: typescript-best-practices description: Provides TypeScript patterns for type-first development, making illegal states unrepresentable, exhaustive handling, and runtime validation. Must use when reading or writing TypeScript/JavaScript files.

TypeScript Best Practices

Pair with React Best Practices

When working with React components (.tsx, .jsx files or @react imports), always load react-best-practices alongside this skill. This skill covers TypeScript fundamentals; React-specific patterns (effects, hooks, refs, component design) are in the dedicated React skill.

Type-First Development

Types define the contract before implementation. Follow this workflow:

  1. Define the data model - types, interfaces, and schemas first
  2. Define function signatures - input/output types before logic
  3. Implement to satisfy types - let the compiler guide completeness
  4. Validate at boundaries - runtime checks where data enters the system

Make Illegal States Unrepresentable

Use the type system to prevent invalid states at compile time.

Discriminated unions for mutually exclusive states:

// Good: only valid combinations possible type RequestState<T> = | { status: 'idle' } | { status: 'loading' } | { status: 'success'; data: T } | { status: 'error'; error: Error }; // Bad: allows invalid combinations like { loading: true, error: Error } type RequestState<T> = { loading: boolean; data?: T; error?: Error; };

Branded types for domain primitives:

type UserId = string & { readonly __brand: 'UserId' }; type OrderId = string & { readonly __brand: 'OrderId' }; // Compiler prevents passing OrderId where UserId expected function getUser(id: UserId): Promise<User> { /* ... */ } function createUserId(id: string): UserId { return id as UserId; }

Const assertions for literal unions:

const ROLES = ['admin', 'user', 'guest'] as const; type Role = typeof ROLES[number]; // 'admin' | 'user' | 'guest' // Array and type stay in sync automatically function isValidRole(role: string): role is Role { return ROLES.includes(role as Role); }

Required vs optional fields - be explicit:

// Creation: some fields required type CreateUser = { email: string; name: string; }; // Update: all fields optional type UpdateUser = Partial<CreateUser>; // Database row: all fields present type User = CreateUser & { id: UserId; createdAt: Date; };

Module Structure

Prefer smaller, focused files: one component, hook, or utility per file. Split when a file handles multiple concerns or exceeds ~200 lines. Colocate tests with implementation (foo.test.ts alongside foo.ts). Group related files by feature rather than by type.

Functional Patterns

  • Prefer const over let; use readonly and Readonly<T> for immutable data.
  • Use array.map/filter/reduce over for loops; chain transformations in pipelines.
  • Write pure functions for business logic; isolate side effects in dedicated modules.
  • Avoid mutating function parameters; return new objects/arrays instead.

Instructions

  • Enable strict mode; model data with interfaces and types. Strong typing catches bugs at compile time.
  • Every code path returns a value or throws; use exhaustive switch with never checks in default. Unhandled cases become compile errors.
  • Propagate errors with context; catching requires re-throwing or returning a meaningful result. Hidden failures delay debugging.
  • Handle edge cases explicitly: empty arrays, null/undefined inputs, boundary values. Defensive checks prevent runtime surprises.
  • Use await for async calls; wrap external calls with contextual error messages. Unhandled rejections crash Node processes.
  • Add or update focused tests when changing logic; test behavior, not implementation details.

Examples

Explicit failure for unimplemented logic:

export function buildWidget(widgetType: string): never { throw new Error(`buildWidget not implemented for type: ${widgetType}`); }

Exhaustive switch with never check:

type Status = "active" | "inactive"; export function processStatus(status: Status): string { switch (status) { case "active": return "processing"; case "inactive": return "skipped"; default: { const _exhaustive: never = status; throw new Error(`unhandled status: ${_exhaustive}`); } } }

Wrap external calls with context:

export async function fetchWidget(id: string): Promise<Widget> { const response = await fetch(`/api/widgets/${id}`); if (!response.ok) { throw new Error(`fetch widget ${id} failed: ${response.status}`); } return response.json(); }

Debug logging with namespaced logger:

import debug from "debug"; const log = debug("myapp:widgets"); export function createWidget(name: string): Widget { log("creating widget: %s", name); const widget = { id: crypto.randomUUID(), name }; log("created widget: %s", widget.id); return widget; }

Runtime Validation with Zod

  • Define schemas as single source of truth; infer TypeScript types with z.infer<>. Avoid duplicating types and schemas.
  • Use safeParse for user input where failure is expected; use parse at trust boundaries where invalid data is a bug.
  • Compose schemas with .extend(), .pick(), .omit(), .merge() for DRY definitions.
  • Add .transform() for data normalization at parse time (trim strings, parse dates).
  • Include descriptive error messages; use .refine() for custom validation logic.

Examples

Schema as source of truth with type inference:

import { z } from "zod"; const UserSchema = z.object({ id: z.string().uuid(), email: z.string().email(), name: z.string().min(1), createdAt: z.string().transform((s) => new Date(s)), }); type User = z.infer<typeof UserSchema>;

Return parse results to callers (never swallow errors):

import { z, SafeParseReturnType } from "zod"; export function parseUserInput(raw: unknown): SafeParseReturnType<unknown, User> { return UserSchema.safeParse(raw); } // Caller handles both success and error: const result = parseUserInput(formData); if (!result.success) { setErrors(result.error.flatten().fieldErrors); return; } await submitUser(result.data);

Strict parsing at trust boundaries:

export async function fetchUser(id: string): Promise<User> { const response = await fetch(`/api/users/${id}`); if (!response.ok) { throw new Error(`fetch user ${id} failed: ${response.status}`); } const data = await response.json(); return UserSchema.parse(data); // throws if API contract violated }

Schema composition:

const CreateUserSchema = UserSchema.omit({ id: true, createdAt: true }); const UpdateUserSchema = CreateUserSchema.partial(); const UserWithPostsSchema = UserSchema.extend({ posts: z.array(PostSchema), });

Configuration

  • Load config from environment variables at startup; validate with Zod before use. Invalid config should crash immediately.
  • Define a typed config object as single source of truth; avoid accessing process.env throughout the codebase.
  • Use sensible defaults for development; require explicit values for production secrets.

Examples

Typed config with Zod validation:

import { z } from "zod"; const ConfigSchema = z.object({ PORT: z.coerce.number().default(3000), DATABASE_URL: z.string().url(), API_KEY: z.string().min(1), NODE_ENV: z.enum(["development", "production", "test"]).default("development"), }); export const config = ConfigSchema.parse(process.env);

Access config values (not process.env directly):

import { config } from "./config"; const server = app.listen(config.PORT); const db = connect(config.DATABASE_URL);

Optional: type-fest

For advanced type utilities beyond TypeScript builtins, consider type-fest:

  • Opaque<T, Token> - cleaner branded types than manual & { __brand } pattern
  • PartialDeep<T> - recursive partial for nested objects
  • ReadonlyDeep<T> - recursive readonly for immutable data
  • LiteralUnion<Literals, Fallback> - literals with autocomplete + string fallback
  • SetRequired<T, K> / SetOptional<T, K> - targeted field modifications
  • Simplify<T> - flatten complex intersection types in IDE tooltips
import type { Opaque, PartialDeep, SetRequired } from 'type-fest'; // Branded type (cleaner than manual approach) type UserId = Opaque<string, 'UserId'>; // Deep partial for patch operations type UserPatch = PartialDeep<User>; // Make specific fields required type UserWithEmail = SetRequired<Partial<User>, 'email'>;
五维分析
清晰度9/10
创新性6/10
实用性8/10
完整性7/10
可维护性8/10
优缺点分析

优点

  • 全面的TypeScript模式
  • 强调类型安全
  • 良好的Zod集成示例
  • 清晰的函数式编程指导

缺点

  • 没有实际可执行代码
  • 需要外部依赖
  • 假设已有TypeScript知识
  • 仅限于概念指导

相关技能

openai-agents-js

A
toolCo-Pilot / 辅助式
84/ 100

“看起来很能打,但别让配置把人劝退。”

mastra

A
toolCode Lib / 代码库
84/ 100

“这是 AI 智能体的瑞士军刀,但你需要一本说明书才能搞清楚哪片刀片是用来切 LLM 的,哪片是用来撬开顽固的工作流的。”

basilic

A
toolCo-Pilot / 辅助式
82/ 100

“看起来很能打,但别让配置把人劝退。”

免责声明:本内容来源于 GitHub 开源项目,仅供展示和评分分析使用。

版权归原作者所有 0xBigBoss.