typescript
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTypeScript
TypeScript
TypeScript-specific guidelines for type safety and code organization.
面向类型安全与代码组织的TypeScript专属指南。
Quick Reference
快速参考
Do
建议
- Use for type-only imports
import type - Use when re-throwing errors
{ cause } - Let TypeScript infer types when obvious
- Create factory functions with prefix
create* - Prefer factory functions over classes
- Return from handlers when request doesn't match
null - Use a logger instead of
console.log
- 仅类型导入时使用
import type - 重新抛出错误时使用
{ cause } - 类型明显时让TypeScript自动推断
- 创建工厂函数时使用前缀
create* - 优先使用工厂函数而非类
- 当请求不匹配时,处理器返回
null - 使用日志工具而非
console.log
Don't
禁忌
- Use default exports
- Use type (use
anyand narrow)unknown - Use type assertions () - they indicate interface problems
as Type - Over-type code with explicit annotations the compiler can infer
- Include file extensions in imports (unless required by runtime)
- 不要使用默认导出
- 不要使用类型(改用
any并收窄类型)unknown - 不要使用类型断言()——这通常意味着接口存在问题
as Type - 编译器可自动推断时,不要过度添加显式类型注解
- 导入时不要包含文件扩展名(除非运行时要求)
Naming Conventions
命名规范
Files
文件
| Type | Convention | Example |
|---|---|---|
| Regular modules | Lowercase, hyphens for multi-word | |
| Single-word modules | Lowercase | |
| Test files | | |
| 类型 | 规范 | 示例 |
|---|---|---|
| 常规模块 | 小写,多词用连字符分隔 | |
| 单字模块 | 小写 | |
| 测试文件 | | |
Types and Interfaces
类型与接口
| Pattern | Use Case | Example |
|---|---|---|
| Interfaces, type aliases | |
| Function arguments | |
| API responses | |
| Data structures | |
| Handler interfaces | |
| 模式 | 使用场景 | 示例 |
|---|---|---|
| 接口、类型别名 | |
| 函数参数 | |
| API响应 | |
| 数据结构 | |
| 处理器接口 | |
Functions
函数
| Pattern | Use Case | Example |
|---|---|---|
| All functions | |
| Factory functions | |
| Boolean predicates | |
| Retrieval without side effects | |
| Search/lookup operations | |
| Builder/generator functions | |
| Event/request handlers | |
| 模式 | 使用场景 | 示例 |
|---|---|---|
| 所有函数 | |
| 工厂函数 | |
| 布尔谓词函数 | |
| 无副作用的检索函数 | |
| 搜索/查找操作函数 | |
| 构建/生成器函数 | |
| 事件/请求处理器函数 | |
Variables
变量
| Pattern | Use Case | Example |
|---|---|---|
| Regular variables | |
| Constants, environment vars | |
| Unused parameters | |
| 模式 | 使用场景 | 示例 |
|---|---|---|
| 常规变量 | |
| 常量、环境变量 | |
| 未使用的参数 | |
Acronyms in Names
名称中的缩写词
Preserve acronym capitalization based on position:
// Good - acronyms stay capitalized when starting uppercase
getURLFromRequest
requestURL
parseHTTPHeaders
// Good - lowercase-starting acronyms stay lowercase
url
json
// Bad - don't mix case within acronyms
getUrlFromRequest // Should be getURLFromRequest
requestUrl // Should be requestURLCommon acronyms: URL, HTTP, HTTPS, JSON, API, RPC, HTML, XML
Note: "ID" is an abbreviation, so use standard camelCase: , , .
userIdrequestIdgetId()根据位置保留缩写词的大小写:
// 规范 - 缩写词在以大写开头时保持大写
getURLFromRequest
requestURL
parseHTTPHeaders
// 规范 - 以小写开头的缩写词保持小写
url
json
// 不规范 - 不要在缩写词内部混合大小写
getUrlFromRequest // 应改为 getURLFromRequest
requestUrl // 应改为 requestURL常见缩写词:URL, HTTP, HTTPS, JSON, API, RPC, HTML, XML
注意:"ID"是缩写,因此使用标准小驼峰命名:, , 。
userIdrequestIdgetId()Type System Patterns
类型系统模式
Runtime Validation
运行时校验
Use a validation library (e.g., arktype, zod) for runtime type validation. Define the validator and TypeScript type together:
typescript
import { type } from "arktype";
// Define runtime validator
export const PaymentRequest = type({
scheme: "string",
network: "string",
amount: "string.numeric",
resource: "string.url",
});
// Derive TypeScript type from validator
export type PaymentRequest = typeof PaymentRequest.infer;使用校验库(如arktype、zod)进行运行时类型校验。同时定义校验器与TypeScript类型:
typescript
import { type } from "arktype";
// 定义运行时校验器
export const PaymentRequest = type({
scheme: "string",
network: "string",
amount: "string.numeric",
resource: "string.url",
});
// 从校验器派生TypeScript类型
export type PaymentRequest = typeof PaymentRequest.infer;Type Guards
类型守卫
Create type guards using validation functions:
typescript
export function isAddress(maybe: unknown): maybe is Address {
return !isValidationError(Address(maybe));
}
export function isKnownNetwork(n: string): n is KnownNetwork {
return knownNetworks.includes(n as KnownNetwork);
}使用校验函数创建类型守卫:
typescript
export function isAddress(maybe: unknown): maybe is Address {
return !isValidationError(Address(maybe));
}
export function isKnownNetwork(n: string): n is KnownNetwork {
return knownNetworks.includes(n as KnownNetwork);
}Interfaces vs Types
接口 vs 类型
- : Use for data structures, unions, and validator-derived types
type - : Use for behavioral contracts (objects with methods)
interface
typescript
// Type for data structure
export type RequestContext = {
request: RequestInfo | URL;
};
// Interface for behavioral contract
export interface PaymentHandler {
getSupported?: () => Promise<SupportedKind>[];
handleSettle: (requirements, payment) => Promise<SettleResponse | null>;
}- :用于数据结构、联合类型及校验器派生类型
type - :用于行为契约(包含方法的对象)
interface
typescript
// 用于数据结构的type
export type RequestContext = {
request: RequestInfo | URL;
};
// 用于行为契约的interface
export interface PaymentHandler {
getSupported?: () => Promise<SupportedKind>[];
handleSettle: (requirements, payment) => Promise<SettleResponse | null>;
}Const Assertions for Exhaustive Types
用于穷尽类型的Const断言
Use for exhaustive literal types:
as consttypescript
const PaymentMode = {
Direct: "direct",
Deferred: "deferred",
} as const;
type PaymentMode = (typeof PaymentMode)[keyof typeof PaymentMode];
// TypeScript ensures all cases handled in switch
switch (mode) {
case PaymentMode.Direct:
// ...
break;
case PaymentMode.Deferred:
// ...
break;
}使用定义穷尽字面量类型:
as consttypescript
const PaymentMode = {
Direct: "direct",
Deferred: "deferred",
} as const;
type PaymentMode = (typeof PaymentMode)[keyof typeof PaymentMode];
// TypeScript会确保switch语句覆盖所有情况
switch (mode) {
case PaymentMode.Direct:
// ...
break;
case PaymentMode.Deferred:
// ...
break;
}Type-Only Imports
仅类型导入
Use for type-only imports:
import typetypescript
import type { PaymentRequest } from "./types";
import type { Hex, Account } from "viem";
// Mixed imports
import {
type Transaction,
createTransaction, // value import
} from "./transactions";仅类型导入时使用:
import typetypescript
import type { PaymentRequest } from "./types";
import type { Hex, Account } from "viem";
// 混合导入
import {
type Transaction,
createTransaction, // 值导入
} from "./transactions";Avoid Over-Typing
避免过度类型化
Let TypeScript infer types when obvious:
typescript
// Good - return type is obvious
const createHandler = async (network: string) => {
const config = { network, enabled: true };
return {
getConfig: () => config,
isEnabled: () => config.enabled,
};
};
// Unnecessary - the return type is obvious
const createHandler = async (network: string): Promise<{
getConfig: () => { network: string; enabled: boolean };
isEnabled: () => boolean;
}> => { ... };When to add explicit types:
- Public API boundaries where the type serves as documentation
- When the inferred type would be too wide
- When TypeScript cannot infer the type correctly
- Complex return types that benefit from explicit documentation
When NOT to add explicit types:
- Variable assignments with obvious literal values
- Return types that match a simple expression
- Loop variables and intermediate calculations
- Arrow function parameters in callbacks where context provides types
类型明显时让TypeScript自动推断:
typescript
// 规范 - 返回类型清晰明了
const createHandler = async (network: string) => {
const config = { network, enabled: true };
return {
getConfig: () => config,
isEnabled: () => config.enabled,
};
};
// 冗余 - 返回类型已清晰明了
const createHandler = async (network: string): Promise<{
getConfig: () => { network: string; enabled: boolean };
isEnabled: () => boolean;
}> => { ... };何时添加显式类型:
- 公共API边界,类型可作为文档
- 推断类型过宽时
- TypeScript无法正确推断类型时
- 复杂返回类型可通过显式注解提升可读性时
何时不添加显式类型:
- 具有明显字面量值的变量赋值
- 匹配简单表达式的返回类型
- 循环变量与中间计算值
- 回调函数中上下文已提供类型的箭头函数参数
Avoiding any
and Type Assertions
any避免使用any
与类型断言
anyUse instead of when the type is truly unknown, then narrow with validation:
unknownanytypescript
// Bad
function processData(data: any) {
return data.value;
}
// Good
function processData(data: unknown) {
const validated = MyDataType(data);
if (isValidationError(validated)) {
throw new Error(`Invalid data: ${validated.summary}`);
}
return validated.value;
}Type assertions () bypass type checking. They often indicate interface problems. Prefer runtime validation:
as Typetypescript
// Bad
const data = (await response.json()) as UserData;
// Good
const raw = await response.json();
const data = UserData(raw);
if (isValidationError(data)) {
throw new Error(`Invalid response: ${data.summary}`);
}当类型真正未知时,使用替代,然后通过校验收窄类型:
unknownanytypescript
// 不规范
function processData(data: any) {
return data.value;
}
// 规范
function processData(data: unknown) {
const validated = MyDataType(data);
if (isValidationError(validated)) {
throw new Error(`无效数据: ${validated.summary}`);
}
return validated.value;
}类型断言()会绕过类型检查,通常意味着接口存在问题。优先使用运行时校验:
as Typetypescript
// 不规范
const data = (await response.json()) as UserData;
// 规范
const raw = await response.json();
const data = UserData(raw);
if (isValidationError(data)) {
throw new Error(`无效响应: ${data.summary}`);
}Generic Constraints vs Index Signatures
泛型约束 vs 索引签名
Prefer generic type parameters with constraints over index signatures:
typescript
// Bad - index signature (too permissive)
export interface LoggingBackend {
configureApp(args: {
level: LogLevel;
[key: string]: unknown;
}): Promise<void>;
}
// Good - generic with constraint (type-safe)
export type BaseConfigArgs = { level: LogLevel };
export interface LoggingBackend<TConfig extends BaseConfigArgs = BaseConfigArgs> {
configureApp(args: TConfig): Promise<void>;
}优先使用带约束的泛型类型参数而非索引签名:
typescript
// 不规范 - 索引签名(过于宽松)
export interface LoggingBackend {
configureApp(args: {
level: LogLevel;
[key: string]: unknown;
}): Promise<void>;
}
// 规范 - 带约束的泛型(类型安全)
export type BaseConfigArgs = { level: LogLevel };
export interface LoggingBackend<TConfig extends BaseConfigArgs = BaseConfigArgs> {
configureApp(args: TConfig): Promise<void>;
}Import/Export Patterns
导入/导出模式
Barrel Exports
桶导出
Use files to re-export from modules:
index.tstypescript
// packages/types/src/index.ts
// Namespaced exports for grouped functionality
export * as payments from "./payments";
export * as client from "./client";
// Flat exports for utilities
export * from "./validation";
export * from "./helpers";使用文件重新导出模块内容:
index.tstypescript
// packages/types/src/index.ts
// 按功能分组的命名空间导出
export * as payments from "./payments";
export * as client from "./client";
// 工具类的扁平化导出
export * from "./validation";
export * from "./helpers";Named Exports (Preferred)
命名导出(推荐)
typescript
// Good
export function createMiddleware(args: CreateMiddlewareArgs) { ... }
export const MAX_RETRIES = 3;
// Avoid
export default function createMiddleware(args: CreateMiddlewareArgs) { ... }typescript
// 规范
export function createMiddleware(args: CreateMiddlewareArgs) { ... }
export const MAX_RETRIES = 3;
// 避免
export default function createMiddleware(args: CreateMiddlewareArgs) { ... }Import Ordering
导入顺序
Order imports by category:
- External library imports
- Internal package imports
- Relative imports
typescript
// External libraries
import { type } from "arktype";
import { Hono } from "hono";
// Internal packages
import { isValidationError } from "@myorg/types";
import type { Handler } from "@myorg/types/handler";
// Relative imports
import { isValidTransaction } from "./verify";
import { logger } from "./logger";按类别排序导入:
- 外部库导入
- 内部包导入
- 相对路径导入
typescript
// 外部库
import { type } from "arktype";
import { Hono } from "hono";
// 内部包
import { isValidationError } from "@myorg/types";
import type { Handler } from "@myorg/types/handler";
// 相对路径
import { isValidTransaction } from "./verify";
import { logger } from "./logger";Import Paths
导入路径
Omit file extensions in import paths when the module resolver can infer them:
typescript
// Good - no extension needed
import { createHandler } from "./handler";
import type { Config } from "../types";
// Bad - unnecessary extension
import { createHandler } from "./handler.ts";
import type { Config } from "../types.ts";Note: Some environments (like Deno or Node.js with ) require explicit extensions. Follow project conventions when extensions are mandated by the runtime.
"type": "module"当模块解析器可自动推断时,导入路径中省略文件扩展名:
typescript
// 规范 - 无需扩展名
import { createHandler } from "./handler";
import type { Config } from "../types";
// 不规范 - 不必要的扩展名
import { createHandler } from "./handler.ts";
import type { Config } from "../types.ts";注意:部分环境(如Deno或设置的Node.js)要求显式扩展名。当运行时强制要求时,请遵循项目规范。
"type": "module"Async Patterns
异步模式
Factory Functions
工厂函数
Use async factory functions that return objects with async methods:
typescript
const createHandler = async (network: string, rpc: RpcClient, config?: HandlerOptions) => {
// Async initialization
const networkInfo = await fetchNetworkInfo(rpc);
// Return object with async methods
return {
getSupported,
handleVerify,
handleSettle,
};
};使用异步工厂函数返回包含异步方法的对象:
typescript
const createHandler = async (network: string, rpc: RpcClient, config?: HandlerOptions) => {
// 异步初始化
const networkInfo = await fetchNetworkInfo(rpc);
// 返回包含异步方法的对象
return {
getSupported,
handleVerify,
handleSettle,
};
};Parallel Execution
并行执行
Use for independent parallel operations:
Promise.alltypescript
const [tokenName, tokenVersion] = await Promise.all([
client.readContract({ functionName: "name" }),
client.readContract({ functionName: "version" }),
]);使用执行独立的并行操作:
Promise.alltypescript
const [tokenName, tokenVersion] = await Promise.all([
client.readContract({ functionName: "name" }),
client.readContract({ functionName: "version" }),
]);Timeouts
超时处理
Use for operations that need timeouts:
Promise.racetypescript
function timeout(timeoutMs: number, msg?: string) {
return new Promise((_, reject) =>
setTimeout(() => reject(new Error(msg ?? "timed out")), timeoutMs),
);
}
const result = await Promise.race([
fetchData(),
timeout(5000, "fetch timed out"),
]);使用处理需要超时的操作:
Promise.racetypescript
function timeout(timeoutMs: number, msg?: string) {
return new Promise((_, reject) =>
setTimeout(() => reject(new Error(msg ?? "超时")), timeoutMs),
);
}
const result = await Promise.race([
fetchData(),
timeout(5000, "请求超时"),
]);Retry Logic
重试逻辑
Implement retries with exponential backoff:
typescript
let attempt = (options.retryCount ?? 2) + 1;
let backoff = options.initialRetryDelay ?? 100;
let response;
do {
response = await makeRequest();
if (response.ok) {
return response;
}
await new Promise((resolve) => setTimeout(resolve, backoff));
backoff *= 2;
} while (--attempt > 0);实现带指数退避的重试机制:
typescript
let attempt = (options.retryCount ?? 2) + 1;
let backoff = options.initialRetryDelay ?? 100;
let response;
do {
response = await makeRequest();
if (response.ok) {
return response;
}
await new Promise((resolve) => setTimeout(resolve, backoff));
backoff *= 2;
} while (--attempt > 0);Error Handling
错误处理
Validation Errors
校验错误
Check validation errors before proceeding:
typescript
const payload = parsePayload(input);
if (isValidationError(payload)) {
logger.debug(`couldn't validate payload: ${payload.summary}`);
return sendBadRequest();
}
// payload is now typed correctly继续执行前先检查校验错误:
typescript
const payload = parsePayload(input);
if (isValidationError(payload)) {
logger.debug(`无法校验负载: ${payload.summary}`);
return sendBadRequest();
}
// 此时payload已被正确类型化Local Error Response Factories
本地错误响应工厂
Create local helpers for consistent error responses:
typescript
const handleSettle = async (requirements, payment) => {
const errorResponse = (msg: string): SettleResponse => {
logger.error(msg);
return {
success: false,
error: msg,
txHash: null,
};
};
if (someConditionFails) {
return errorResponse("Invalid transaction");
}
// ...
};创建本地辅助函数以生成一致的错误响应:
typescript
const handleSettle = async (requirements, payment) => {
const errorResponse = (msg: string): SettleResponse => {
logger.error(msg);
return {
success: false,
error: msg,
txHash: null,
};
};
if (someConditionFails) {
return errorResponse("无效交易");
}
// ...
};Error Chaining
错误链式处理
Use when re-throwing errors:
{ cause }typescript
try {
transaction = parseTransaction(input);
} catch (cause) {
throw new Error("Failed to parse transaction", { cause });
}重新抛出错误时使用:
{ cause }typescript
try {
transaction = parseTransaction(input);
} catch (cause) {
throw new Error("解析交易失败", { cause });
}Return null
for "Not My Responsibility"
null非职责范围内返回null
nullHandlers should return when a request doesn't match their criteria:
nulltypescript
const handleVerify = async (requirements, payment) => {
if (!isMatchingRequirement(requirements)) {
return null; // Let another handler try
}
// Handle the request...
};当请求不符合处理器的处理条件时,应返回:
nulltypescript
const handleVerify = async (requirements, payment) => {
if (!isMatchingRequirement(requirements)) {
return null; // 交由其他处理器尝试处理
}
// 处理请求...
};Testing
测试
Philosophy
测试理念
Focus test coverage on logic specific to your codebase:
- Business logic and domain-specific validation
- Integration points between components
- Error handling paths and edge cases
- Custom algorithms and data transformations
Do not write tests that merely verify functionality provided by external libraries. Trust well-maintained libraries to do their job.
聚焦于代码库专属逻辑的测试覆盖:
- 业务逻辑与领域专属校验
- 组件间的集成点
- 错误处理路径与边缘情况
- 自定义算法与数据转换
不要编写仅验证外部库功能的测试。信任维护良好的外部库的功能。
Test Structure
测试结构
typescript
import t from "tap";
await t.test("descriptiveTestName", async (t) => {
// Setup
const cache = new Cache({ capacity: 3 });
// Assertions
t.equal(cache.size, 0);
t.matchOnly(cache.get("key"), undefined);
t.end();
});typescript
import t from "tap";
await t.test("描述性测试名称", async (t) => {
// 初始化
const cache = new Cache({ capacity: 3 });
// 断言
t.equal(cache.size, 0);
t.matchOnly(cache.get("key"), undefined);
t.end();
});Time-Based Testing
基于时间的测试
Inject time functions for deterministic time-based tests:
typescript
let theTime = 0;
const now = () => theTime;
const cache = new Cache({
maxAge: 1000,
now, // Inject time function
});
theTime += 500;
t.matchOnly(cache.get("key"), 42); // Still valid
theTime += 1000;
t.matchOnly(cache.get("key"), undefined); // Expired注入时间函数以实现确定性的基于时间的测试:
typescript
let theTime = 0;
const now = () => theTime;
const cache = new Cache({
maxAge: 1000,
now, // 注入时间函数
});
theTime += 500;
t.matchOnly(cache.get("key"), 42); // 仍有效
theTime += 1000;
t.matchOnly(cache.get("key"), undefined); // 已过期Documentation
文档
TSDoc Comments
TSDoc注释
Document public APIs with TSDoc:
typescript
/**
* Creates a handler for the payment scheme.
*
* @param network - The network identifier (e.g., "mainnet", "testnet")
* @param rpc - RPC client
* @param config - Optional configuration options
* @returns Promise resolving to a Handler
*/
export const createHandler = async (
network: string,
rpc: RpcClient,
config?: HandlerOptions,
): Promise<Handler> => { ... };使用TSDoc注释文档化公共API:
typescript
/**
* 创建支付方案的处理器。
*
* @param network - 网络标识符(例如:"mainnet"、"testnet")
* @param rpc - RPC客户端
* @param config - 可选配置项
* @returns 解析为Handler的Promise
*/
export const createHandler = async (
network: string,
rpc: RpcClient,
config?: HandlerOptions,
): Promise<Handler> => { ... };