typescript-best-practices by 0xbigboss/claude-code
npx skills add https://github.com/0xbigboss/claude-code --skill typescript-best-practices在使用 React 组件(.tsx、.jsx 文件或 @react 导入)时,请始终将此技能与 react-best-practices 一同加载。本技能涵盖 TypeScript 基础;React 特有的模式(effects、hooks、refs、组件设计)位于专门的 React 技能中。
类型在实现之前定义契约。遵循以下工作流程:
使用类型系统在编译时防止无效状态。
使用可辨识联合表示互斥状态:
// 良好:只允许有效的组合
type RequestState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: Error };
// 不佳:允许无效组合,如 { loading: true, error: Error }
type RequestState<T> = {
loading: boolean;
data?: T;
error?: Error;
};
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.
Types define the contract before implementation. Follow this workflow:
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;
};
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
使用标记类型表示领域原语:
type UserId = string & { readonly __brand: 'UserId' };
type OrderId = string & { readonly __brand: 'OrderId' };
// 编译器阻止在期望 UserId 的地方传递 OrderId
function getUser(id: UserId): Promise<User> { /* ... */ }
function createUserId(id: string): UserId {
return id as UserId;
}
使用常量断言表示字面量联合:
const ROLES = ['admin', 'user', 'guest'] as const;
type Role = typeof ROLES[number]; // 'admin' | 'user' | 'guest'
// 数组和类型自动保持同步
function isValidRole(role: string): role is Role {
return ROLES.includes(role as Role);
}
必需字段与可选字段 - 明确指定:
// 创建:某些字段必需
type CreateUser = {
email: string;
name: string;
};
// 更新:所有字段可选
type UpdateUser = Partial<CreateUser>;
// 数据库行:所有字段都存在
type User = CreateUser & {
id: UserId;
createdAt: Date;
};
倾向于使用更小、更专注的文件:每个文件一个组件、钩子或工具。当文件处理多个关注点或超过约 200 行时进行拆分。将测试与实现放在一起(foo.test.ts 与 foo.ts 相邻)。按功能而非类型对相关文件进行分组。
const 而非 let;对不可变数据使用 readonly 和 Readonly<T>。array.map/filter/reduce 而非 for 循环;在管道中链接转换。strict 模式;使用接口和类型对数据进行建模。强类型在编译时捕获错误。switch 配合 never 检查。未处理的情况会成为编译错误。await;用上下文错误消息包装外部调用。未处理的拒绝会导致 Node 进程崩溃。对未实现的逻辑显式失败:
export function buildWidget(widgetType: string): never {
throw new Error(`buildWidget not implemented for type: ${widgetType}`);
}
使用 never 检查的穷举 switch:
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}`);
}
}
}
用上下文包装外部调用:
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();
}
使用命名空间日志记录器进行调试日志记录:
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;
}
z.infer<> 推断 TypeScript 类型。避免重复类型和模式。safeParse;在信任边界处使用 parse,因为无效数据属于错误。.extend()、.pick()、.omit()、.merge() 组合模式以实现 DRY 定义。.transform() 在解析时进行数据规范化(修剪字符串、解析日期)。.refine() 实现自定义验证逻辑。作为单一事实来源的模式与类型推断:
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>;
将解析结果返回给调用者(绝不吞没错误):
import { z, SafeParseReturnType } from "zod";
export function parseUserInput(raw: unknown): SafeParseReturnType<unknown, User> {
return UserSchema.safeParse(raw);
}
// 调用者处理成功和错误:
const result = parseUserInput(formData);
if (!result.success) {
setErrors(result.error.flatten().fieldErrors);
return;
}
await submitUser(result.data);
在信任边界处严格解析:
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); // 如果违反 API 契约则抛出异常
}
模式组合:
const CreateUserSchema = UserSchema.omit({ id: true, createdAt: true });
const UpdateUserSchema = CreateUserSchema.partial();
const UserWithPostsSchema = UserSchema.extend({
posts: z.array(PostSchema),
});
process.env。使用 Zod 验证的类型化配置:
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);
访问配置值(不直接访问 process.env):
import { config } from "./config";
const server = app.listen(config.PORT);
const db = connect(config.DATABASE_URL);
对于超出 TypeScript 内置功能的高级类型工具,请考虑使用 type-fest:
Opaque<T, Token> - 比手动 & { __brand } 模式更简洁的标记类型
PartialDeep<T> - 嵌套对象的递归部分类型
ReadonlyDeep<T> - 不可变数据的递归只读类型
LiteralUnion<Literals, Fallback> - 具有自动完成功能的字面量联合 + 字符串回退
SetRequired<T, K> / SetOptional<T, K> - 针对特定字段的修改
Simplify<T> - 在 IDE 工具提示中展平复杂的交叉类型
import type { Opaque, PartialDeep, SetRequired } from 'type-fest';
// 标记类型(比手动方法更简洁) type UserId = Opaque<string, 'UserId'>;
// 用于补丁操作的深度部分类型 type UserPatch = PartialDeep<User>;
// 使特定字段成为必需 type UserWithEmail = SetRequired<Partial<User>, 'email'>;
每周安装量
786
仓库
GitHub 星标数
36
首次出现
2026年1月20日
安全审计
安装于
opencode662
gemini-cli629
codex615
github-copilot595
cursor548
claude-code533
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;
};
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.
const over let; use readonly and Readonly<T> for immutable data.array.map/filter/reduce over for loops; chain transformations in pipelines.strict mode; model data with interfaces and types. Strong typing catches bugs at compile time.switch with never checks in default. Unhandled cases become compile errors.await for async calls; wrap external calls with contextual error messages. Unhandled rejections crash Node processes.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;
}
z.infer<>. Avoid duplicating types and schemas.safeParse for user input where failure is expected; use parse at trust boundaries where invalid data is a bug..extend(), .pick(), .omit(), .merge() for DRY definitions..transform() for data normalization at parse time (trim strings, parse dates)..refine() for custom validation logic.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),
});
process.env throughout the codebase.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);
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'>;
Weekly Installs
786
Repository
GitHub Stars
36
First Seen
Jan 20, 2026
Security Audits
Installed on
opencode662
gemini-cli629
codex615
github-copilot595
cursor548
claude-code533
AI新闻播客制作技能:实时新闻转对话式播客脚本与音频生成
1,200 周安装