typescript

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

TypeScript

TypeScript

TypeScript-specific guidelines for type safety and code organization.
面向类型安全与代码组织的TypeScript专属指南。

Quick Reference

快速参考

Do

建议

  • Use
    import type
    for type-only imports
  • Use
    { cause }
    when re-throwing errors
  • Let TypeScript infer types when obvious
  • Create factory functions with
    create*
    prefix
  • Prefer factory functions over classes
  • Return
    null
    from handlers when request doesn't match
  • Use a logger instead of
    console.log
  • 仅类型导入时使用
    import type
  • 重新抛出错误时使用
    { cause }
  • 类型明显时让TypeScript自动推断
  • 创建工厂函数时使用
    create*
    前缀
  • 优先使用工厂函数而非类
  • 当请求不匹配时,处理器返回
    null
  • 使用日志工具而非
    console.log

Don't

禁忌

  • Use default exports
  • Use
    any
    type (use
    unknown
    and narrow)
  • Use type assertions (
    as Type
    ) - they indicate interface problems
  • 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

文件

TypeConventionExample
Regular modulesLowercase, hyphens for multi-word
token-payment.ts
,
server.ts
Single-word modulesLowercase
cache.ts
,
common.ts
Test files
{name}.test.ts
cache.test.ts
类型规范示例
常规模块小写,多词用连字符分隔
token-payment.ts
,
server.ts
单字模块小写
cache.ts
,
common.ts
测试文件
{name}.test.ts
cache.test.ts

Types and Interfaces

类型与接口

PatternUse CaseExample
PascalCase
Interfaces, type aliases
PaymentHandler
,
RequestConfig
*Args
/
*Opts
Function arguments
CreateHandlerOpts
*Response
API responses
SettleResponse
*Info
Data structures
ChainInfo
,
TokenInfo
*Handler
Handler interfaces
PaymentHandler
模式使用场景示例
PascalCase
接口、类型别名
PaymentHandler
,
RequestConfig
*Args
/
*Opts
函数参数
CreateHandlerOpts
*Response
API响应
SettleResponse
*Info
数据结构
ChainInfo
,
TokenInfo
*Handler
处理器接口
PaymentHandler

Functions

函数

PatternUse CaseExample
camelCase
All functions
handleRequest
create*
Factory functions
createHandler
,
createClient
is*
Boolean predicates
isValidationError
,
isKnownType
get*
Retrieval without side effects
getBalance
,
getConfig
lookup*
Search/lookup operations
lookupToken
,
lookupNetwork
generate*
Builder/generator functions
generateMatcher
,
generateConfig
handle*
Event/request handlers
handleSettle
,
handleVerify
模式使用场景示例
camelCase
所有函数
handleRequest
create*
工厂函数
createHandler
,
createClient
is*
布尔谓词函数
isValidationError
,
isKnownType
get*
无副作用的检索函数
getBalance
,
getConfig
lookup*
搜索/查找操作函数
lookupToken
,
lookupNetwork
generate*
构建/生成器函数
generateMatcher
,
generateConfig
handle*
事件/请求处理器函数
handleSettle
,
handleVerify

Variables

变量

PatternUse CaseExample
camelCase
Regular variables
paymentResponse
,
blockNumber
SCREAMING_SNAKE_CASE
Constants, environment vars
API_BASE_URL
,
MAX_RETRIES
_
prefix
Unused parameters
_ctx
,
_unused
模式使用场景示例
camelCase
常规变量
paymentResponse
,
blockNumber
SCREAMING_SNAKE_CASE
常量、环境变量
API_BASE_URL
,
MAX_RETRIES
_
前缀
未使用的参数
_ctx
,
_unused

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 requestURL
Common acronyms: URL, HTTP, HTTPS, JSON, API, RPC, HTML, XML
Note: "ID" is an abbreviation, so use standard camelCase:
userId
,
requestId
,
getId()
.
根据位置保留缩写词的大小写:
// 规范 - 缩写词在以大写开头时保持大写
getURLFromRequest
requestURL
parseHTTPHeaders

// 规范 - 以小写开头的缩写词保持小写
url
json

// 不规范 - 不要在缩写词内部混合大小写
getUrlFromRequest  // 应改为 getURLFromRequest
requestUrl         // 应改为 requestURL
常见缩写词:URL, HTTP, HTTPS, JSON, API, RPC, HTML, XML
注意:"ID"是缩写,因此使用标准小驼峰命名:
userId
,
requestId
,
getId()

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 类型

  • type
    : Use for data structures, unions, and validator-derived types
  • interface
    : Use for behavioral contracts (objects with methods)
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
as const
for exhaustive literal types:
typescript
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 const
定义穷尽字面量类型:
typescript
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
import type
for type-only imports:
typescript
import type { PaymentRequest } from "./types";
import type { Hex, Account } from "viem";

// Mixed imports
import {
  type Transaction,
  createTransaction, // value import
} from "./transactions";
仅类型导入时使用
import type
typescript
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
与类型断言

Use
unknown
instead of
any
when the type is truly unknown, then narrow with validation:
typescript
// 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 (
as Type
) bypass type checking. They often indicate interface problems. Prefer runtime validation:
typescript
// 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}`);
}
当类型真正未知时,使用
unknown
替代
any
,然后通过校验收窄类型:
typescript
// 不规范
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 Type
)会绕过类型检查,通常意味着接口存在问题。优先使用运行时校验:
typescript
// 不规范
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
index.ts
files to re-export from modules:
typescript
// 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.ts
文件重新导出模块内容:
typescript
// 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:
  1. External library imports
  2. Internal package imports
  3. 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";
按类别排序导入:
  1. 外部库导入
  2. 内部包导入
  3. 相对路径导入
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
"type": "module"
) require explicit extensions. Follow project conventions when extensions are mandated by the runtime.
当模块解析器可自动推断时,导入路径中省略文件扩展名:
typescript
// 规范 - 无需扩展名
import { createHandler } from "./handler";
import type { Config } from "../types";

// 不规范 - 不必要的扩展名
import { createHandler } from "./handler.ts";
import type { Config } from "../types.ts";
注意:部分环境(如Deno或设置
"type": "module"
的Node.js)要求显式扩展名。当运行时强制要求时,请遵循项目规范。

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
Promise.all
for independent parallel operations:
typescript
const [tokenName, tokenVersion] = await Promise.all([
  client.readContract({ functionName: "name" }),
  client.readContract({ functionName: "version" }),
]);
使用
Promise.all
执行独立的并行操作:
typescript
const [tokenName, tokenVersion] = await Promise.all([
  client.readContract({ functionName: "name" }),
  client.readContract({ functionName: "version" }),
]);

Timeouts

超时处理

Use
Promise.race
for operations that need timeouts:
typescript
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.race
处理需要超时的操作:
typescript
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
{ cause }
when re-throwing errors:
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

Handlers should return
null
when a request doesn't match their criteria:
typescript
const handleVerify = async (requirements, payment) => {
  if (!isMatchingRequirement(requirements)) {
    return null; // Let another handler try
  }
  // Handle the request...
};
当请求不符合处理器的处理条件时,应返回
null
typescript
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> => { ... };