howto-code-in-typescript
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTypeScript House Style
TypeScript团队编码风格指南
Overview
概述
Comprehensive TypeScript coding standards emphasizing type safety, immutability, and integration with Functional Core, Imperative Shell (FCIS) pattern.
Core principles:
- Types as documentation and constraints
- Immutability by default prevents bugs
- Explicit over implicit (especially in function signatures)
- Functional Core returns Results, Imperative Shell may throw
- Configuration over decoration/magic
这份全面的TypeScript编码标准强调类型安全、不可变性,以及与Functional Core, Imperative Shell(FCIS)模式的集成。
核心原则:
- 类型作为文档与约束
- 默认不可变性以预防bug
- 显式优于隐式(尤其在函数签名中)
- 函数式核心返回Result类型,命令式外壳可抛出异常
- 配置优于装饰器/魔法特性
Quick Self-Check (Use Under Pressure)
快速自检清单(紧急场景下使用)
When under deadline pressure or focused on other concerns (performance, accuracy, features), STOP and verify:
- Using not
Array<T>T[] - Using not
type(unless class contract)interface - Using math.js for money/currencies/complex math
- Parameters are or
readonlyReadonly<T> - Using not
unknownany - Using for absent values (not
null)undefined - Using function declarations (not const arrow) for top-level functions
- Using named exports (not default exports)
- Using not
===== - Using for numeric arrays
.sort((a, b) => a - b) - Using with explicit radix
parseInt(x, 10)
Why this matters: Under pressure, you'll default to muscle memory. These checks catch the most common violations.
在截止日期临近或专注于其他事项(性能、准确性、功能开发)时,请停下来验证以下内容:
- 使用而非
Array<T>T[] - 使用而非
type(除非是类契约)interface - 货币/金融/复杂数学计算使用math.js
- 参数标记为或
readonlyReadonly<T> - 使用而非
unknownany - 缺失值使用(而非
null)undefined - 顶层函数使用声明(而非箭头函数赋值)
function - 使用具名导出(而非默认导出)
- 使用而非
===== - 数值数组排序使用
.sort((a, b) => a - b) - 需显式指定基数
parseInt(x, 10)
重要性: 紧急情况下,你会依赖肌肉记忆编写代码,这些检查能帮你捕获最常见的规范违规。
Type Declarations
类型声明
Type vs Interface
Type与Interface的选择
Always use except for class contracts.
typetypescript
// GOOD: type for object shapes
type UserData = {
readonly id: string;
name: string;
email: string | null;
};
// GOOD: interface for class contract
interface IUserRepository {
findById(id: string): Promise<User | null>;
}
class UserRepository implements IUserRepository {
// implementation
}
// BAD: interface for object shape
interface UserData {
id: string;
name: string;
}Rationale: Types compose better with unions and intersections, support mapped types, and avoid declaration merging surprises. Interfaces are only for defining what a class must implement.
IMPORTANT: Even when under deadline pressure, even when focused on other concerns (financial accuracy, performance optimization, bug fixes), take 2 seconds to ask: "Is this a class contract?" If no, use . Don't default to out of habit.
typeinterface除非是定义类契约,否则始终使用。
typetypescript
// 规范:用type定义对象结构
type UserData = {
readonly id: string;
name: string;
email: string | null;
};
// 规范:用interface定义类契约
interface IUserRepository {
findById(id: string): Promise<User | null>;
}
class UserRepository implements IUserRepository {
// 实现代码
}
// 不规范:用interface定义对象结构
interface UserData {
id: string;
name: string;
}理由: Type在联合类型、交叉类型的组合上表现更优,支持映射类型,还能避免声明合并带来的意外问题。Interface仅用于定义类必须实现的契约。
重点提示: 即使在紧急场景或专注于其他事项(财务准确性、性能优化、bug修复)时,也要花2秒问自己:“这是类契约吗?”如果不是,就用,不要因为习惯默认使用。
typeinterfaceNaming Conventions
命名规范
Type Suffixes
类型后缀
| Suffix | Usage | Example |
|---|---|---|
| Function parameter objects (3+ args or any optional) | |
| Persistent configuration from storage | |
| Discriminated union return types | |
| Function/callback types | |
| React component props | |
| State objects (component/application) | |
| 后缀 | 用途 | 示例 |
|---|---|---|
| 函数参数对象(参数≥3个或包含可选参数) | |
| 从存储读取的持久化配置 | |
| 可区分联合类型的返回值 | |
| 函数/回调类型 | |
| React组件属性 | |
| 组件/应用状态对象 | |
General Casing
通用大小写规则
| Element | Convention | Example |
|---|---|---|
| Variables & functions | camelCase | |
| Types & classes | PascalCase | |
| Constants | UPPER_CASE | |
| Files | kebab-case | |
| 元素 | 规范 | 示例 |
|---|---|---|
| 变量与函数 | camelCase | |
| 类型与类 | PascalCase | |
| 常量 | UPPER_CASE | |
| 文件 | kebab-case | |
Boolean Naming
布尔值命名
Use is/has/can/should/will prefixes. Avoid negative names.
typescript
// GOOD
const isActive = true;
const hasPermission = checkPermission();
const canEdit = user.role === 'admin';
const shouldRetry = attempts < MAX_RETRIES;
const willTimeout = elapsed > threshold;
// Also acceptable: adjectives for state
type User = {
active: boolean;
visible: boolean;
disabled: boolean;
};
// BAD: negative names
const isDisabled = false; // prefer isEnabled
const notReady = true; // prefer isReady使用is/has/can/should/will前缀,避免使用否定式命名。
typescript
// 规范
const isActive = true;
const hasPermission = checkPermission();
const canEdit = user.role === 'admin';
const shouldRetry = attempts < MAX_RETRIES;
const willTimeout = elapsed > threshold;
// 也可接受:用形容词表示状态
type User = {
active: boolean;
visible: boolean;
disabled: boolean;
};
// 不规范:否定式命名
const isDisabled = false; // 优先使用isEnabled
const notReady = true; // 优先使用isReadyType Suffix Details
类型后缀细节
FooOptions - Parameter Objects
FooOptions - 参数对象
Use for functions with 3+ arguments OR any optional arguments.
typescript
type ProcessUserOptions = {
readonly name: string;
readonly email: string;
readonly age: number;
readonly sendWelcome?: boolean;
};
// GOOD: destructure in body, not in parameters
function processUser(options: ProcessUserOptions): void {
const {name, email, age, sendWelcome = true} = options;
// implementation
}
// BAD: inline destructuring in parameters
function processUser({name, email, age}: {name: string, email: string, age: number}) {
// causes duplication when destructuring
}
// BAD: not using options pattern for 3+ args
function processUser(name: string, email: string, age: number, sendWelcome?: boolean) {
// hard to call, positional arguments
}当函数参数≥3个或包含可选参数时,使用参数对象。
typescript
type ProcessUserOptions = {
readonly name: string;
readonly email: string;
readonly age: number;
readonly sendWelcome?: boolean;
};
// 规范:在函数体内解构,而非参数中
function processUser(options: ProcessUserOptions): void {
const {name, email, age, sendWelcome = true} = options;
// 实现代码
}
// 不规范:参数中直接解构
function processUser({name, email, age}: {name: string, email: string, age: number}) {
// 解构时会导致代码重复
}
// 不规范:参数≥3个时未使用options模式
function processUser(name: string, email: string, age: number, sendWelcome?: boolean) {
// 调用困难,依赖参数位置
}FooResult - Discriminated Unions
FooResult - 可区分联合类型
Always use discriminated unions for Result types. Integrate with neverthrow.
typescript
// GOOD: discriminated union with success/error
type ValidationResult =
| { success: true; data: ValidUser }
| { success: false; error: ValidationError };
// GOOD: use neverthrow for Result types
import {Result, ok, err} from 'neverthrow';
type ValidationError = {
field: string;
message: string;
};
function validateUser(data: Readonly<UserData>): Result<ValidUser, ValidationError> {
if (!data.email) {
return err({field: 'email', message: 'Email is required'});
}
return ok({...data, validated: true});
}
// Usage
const result = validateUser(userData);
if (result.isOk()) {
console.log(result.value); // ValidUser
} else {
console.error(result.error); // ValidationError
}Rule: Functional Core functions should return types. Imperative Shell functions may throw exceptions for HTTP errors and similar.
Result<T, E>Result类型始终使用可区分联合类型,结合neverthrow库使用。
typescript
// 规范:带success/error标记的可区分联合类型
type ValidationResult =
| { success: true; data: ValidUser }
| { success: false; error: ValidationError };
// 规范:用neverthrow定义Result类型
import {Result, ok, err} from 'neverthrow';
type ValidationError = {
field: string;
message: string;
};
function validateUser(data: Readonly<UserData>): Result<ValidUser, ValidationError> {
if (!data.email) {
return err({field: 'email', message: '邮箱为必填项'});
}
return ok({...data, validated: true});
}
// 使用示例
const result = validateUser(userData);
if (result.isOk()) {
console.log(result.value); // ValidUser类型
} else {
console.error(result.error); // ValidationError类型
}规则: 函数式核心函数应返回类型。命令式外壳函数可针对HTTP错误等场景抛出异常。
Result<T, E>Functions
函数
Declaration Style
声明风格
Use declarations for top-level functions. Use arrow functions for inline callbacks.
functiontypescript
// GOOD: function declaration for top-level
function processUser(data: Readonly<UserData>): ProcessResult {
return {success: true, user: data};
}
// GOOD: arrow functions for inline callbacks
const users = rawData.map(u => transformUser(u));
button.addEventListener('click', (e) => handleClick(e));
fetch(url).then(data => processData(data));
// BAD: const arrow for top-level function
const processUser = (data: UserData): ProcessResult => {
return {success: true, user: data};
};Rationale: Function declarations are hoisted and more visible. Arrow functions capture lexical and are concise for callbacks.
this顶层函数使用声明,内联回调使用箭头函数。
functiontypescript
// 规范:顶层函数用function声明
function processUser(data: Readonly<UserData>): ProcessResult {
return {success: true, user: data};
}
// 规范:内联回调用箭头函数
const users = rawData.map(u => transformUser(u));
button.addEventListener('click', (e) => handleClick(e));
fetch(url).then(data => processData(data));
// 不规范:顶层函数用箭头函数赋值
const processUser = (data: UserData): ProcessResult => {
return {success: true, user: data};
};理由: function声明会被提升,可读性更强。箭头函数会捕获词法作用域的,且作为回调时更简洁。
thisConst Arrow Functions
箭头函数赋值
Use declarations only for stable references.
const foo = () => {}typescript
// GOOD: stable reference for React hooks
const handleSubmit = (event: FormEvent) => {
event.preventDefault();
// implementation
};
useEffect(() => {
// handleSubmit reference is stable
}, [handleSubmit]);
// GOOD: long event listener passed from variable
const handleComplexClick = (event: MouseEvent) => {
// many lines of logic
};
element.addEventListener('click', handleComplexClick);
// BAD: const arrow for regular top-level function
const calculateTotal = (items: Array<Item>): number => {
return items.reduce((sum, item) => sum + item.price, 0);
};
// GOOD: use function declaration
function calculateTotal(items: ReadonlyArray<Item>): number {
return items.reduce((sum, item) => sum + item.price, 0);
}仅当需要稳定引用时,使用声明。
const foo = () => {}typescript
// 规范:React Hooks中使用稳定引用
const handleSubmit = (event: FormEvent) => {
event.preventDefault();
// 实现代码
};
useEffect(() => {
// handleSubmit引用稳定
}, [handleSubmit]);
// 规范:复杂事件监听器用变量存储
const handleComplexClick = (event: MouseEvent) => {
// 多行逻辑代码
};
element.addEventListener('click', handleComplexClick);
// 不规范:普通顶层函数用箭头函数
const calculateTotal = (items: Array<Item>): number => {
return items.reduce((sum, item) => sum + item.price, 0);
};
// 规范:使用function声明
function calculateTotal(items: ReadonlyArray<Item>): number {
return items.reduce((sum, item) => sum + item.price, 0);
}Parameter Objects
参数对象
Use parameter objects for 3+ arguments OR any optional arguments.
typescript
// GOOD: options object for 3+ args
type CreateUserOptions = {
readonly name: string;
readonly email: string;
readonly age: number;
readonly newsletter?: boolean;
};
function createUser(options: CreateUserOptions): User {
const {name, email, age, newsletter = false} = options;
// implementation
}
// GOOD: 2 args, but one is optional - use options
type SendEmailOptions = {
readonly to: string;
readonly subject: string;
readonly body?: string;
};
function sendEmail(options: SendEmailOptions): void {
// implementation
}
// GOOD: 2 required args - no options needed
function divide(numerator: number, denominator: number): number {
return numerator / denominator;
}当函数参数≥3个或包含可选参数时,使用参数对象。
typescript
// 规范:参数≥3个时用options对象
type CreateUserOptions = {
readonly name: string;
readonly email: string;
readonly age: number;
readonly newsletter?: boolean;
};
function createUser(options: CreateUserOptions): User {
const {name, email, age, newsletter = false} = options;
// 实现代码
}
// 规范:2个参数但含可选参数时用options对象
type SendEmailOptions = {
readonly to: string;
readonly subject: string;
readonly body?: string;
};
function sendEmail(options: SendEmailOptions): void {
// 实现代码
}
// 规范:2个必填参数,无需options对象
function divide(numerator: number, denominator: number): number {
return numerator / denominator;
}Async Functions
异步函数
Always explicitly type Promise returns. Avoid async void.
typescript
// GOOD: explicit Promise return type
async function fetchUser(id: string): Promise<User> {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
// GOOD: Promise<void> for side effects
async function saveUser(user: User): Promise<void> {
await fetch('/api/users', {
method: 'POST',
body: JSON.stringify(user),
});
}
// BAD: implicit return type
async function fetchUser(id: string) {
const response = await fetch(`/api/users/${id}`);
return response.json();
}Prefer async/await over chains.
.then()typescript
// GOOD: async/await
async function processUserData(id: string): Promise<ProcessedUser> {
const user = await fetchUser(id);
const enriched = await enrichUserData(user);
return transformUser(enriched);
}
// BAD: promise chains
function processUserData(id: string): Promise<ProcessedUser> {
return fetchUser(id)
.then(user => enrichUserData(user))
.then(enriched => transformUser(enriched));
}始终显式声明Promise返回类型,避免async void。
typescript
// 规范:显式声明Promise返回类型
async function fetchUser(id: string): Promise<User> {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
// 规范:副作用函数返回Promise<void>
async function saveUser(user: User): Promise<void> {
await fetch('/api/users', {
method: 'POST',
body: JSON.stringify(user),
});
}
// 不规范:返回类型隐式
async function fetchUser(id: string) {
const response = await fetch(`/api/users/${id}`);
return response.json();
}优先使用async/await而非链式调用。
.then()typescript
// 规范:使用async/await
async function processUserData(id: string): Promise<ProcessedUser> {
const user = await fetchUser(id);
const enriched = await enrichUserData(user);
return transformUser(enriched);
}
// 不规范:Promise链式调用
function processUserData(id: string): Promise<ProcessedUser> {
return fetchUser(id)
.then(user => enrichUserData(user))
.then(enriched => transformUser(enriched));
}When to Use Async
何时使用异步
Be selective with async. Not everything needs to be async. Sync code is simpler to reason about and debug.
Use async for:
- Network requests, database operations, file I/O
- Operations that benefit from concurrent execution (Promise.all)
- External service calls
Stay sync for:
- Pure calculations and transformations
- Simple data structure operations
- Code that doesn't touch external systems
typescript
// GOOD: sync for pure transformation
function transformUser(user: User): TransformedUser {
return {
fullName: `${user.firstName} ${user.lastName}`,
email: user.email.toLowerCase(),
};
}
// GOOD: async for I/O
async function loadAndTransformUser(id: string): Promise<TransformedUser> {
const user = await fetchUser(id);
return transformUser(user); // Sync call inside async function is fine
}
// BAD: unnecessary async
async function transformUser(user: User): Promise<TransformedUser> {
return {
fullName: `${user.firstName} ${user.lastName}`,
email: user.email.toLowerCase(),
};
}Why this matters: Async adds complexity—error propagation, cleanup, and stack traces become harder to follow. Keep the async boundary as close to the I/O as possible.
谨慎使用异步,并非所有场景都需要。同步代码更易推理和调试。
使用异步的场景:
- 网络请求、数据库操作、文件I/O
- 可并发执行的操作(使用Promise.all)
- 外部服务调用
保持同步的场景:
- 纯计算与转换逻辑
- 简单数据结构操作
- 不涉及外部系统的代码
typescript
// 规范:纯转换逻辑用同步函数
function transformUser(user: User): TransformedUser {
return {
fullName: `${user.firstName} ${user.lastName}`,
email: user.email.toLowerCase(),
};
}
// 规范:I/O操作使用异步
async function loadAndTransformUser(id: string): Promise<TransformedUser> {
const user = await fetchUser(id);
return transformUser(user); // 异步函数内可调用同步函数
}
// 不规范:不必要的异步
async function transformUser(user: User): Promise<TransformedUser> {
return {
fullName: `${user.firstName} ${user.lastName}`,
email: user.email.toLowerCase(),
};
}理由: 异步会增加复杂度——错误传播、资源清理和堆栈追踪都会变得更难处理。应将异步边界尽可能靠近I/O操作。
Classes
类
When to Use Classes
何时使用类
Prefer functions over classes, EXCEPT for dependency injection patterns.
typescript
// GOOD: class as dependency container
class UserService {
constructor(
private readonly db: Database,
private readonly logger: Logger,
private readonly cache: Cache,
) {}
async getUser(id: string): Promise<User | null> {
this.logger.info(`Fetching user ${id}`);
const cached = await this.cache.get(`user:${id}`);
if (cached) return cached;
const user = await this.db.users.findById(id);
if (user) await this.cache.set(`user:${id}`, user);
return user;
}
}
// BAD: class with no dependencies
class MathUtils {
add(a: number, b: number): number {
return a + b;
}
}
// GOOD: plain functions
function add(a: number, b: number): number {
return a + b;
}优先使用函数而非类,除非是依赖注入场景。
typescript
// 规范:类作为依赖容器
class UserService {
constructor(
private readonly db: Database,
private readonly logger: Logger,
private readonly cache: Cache,
) {}
async getUser(id: string): Promise<User | null> {
this.logger.info(`获取用户 ${id}`);
const cached = await this.cache.get(`user:${id}`);
if (cached) return cached;
const user = await this.db.users.findById(id);
if (user) await this.cache.set(`user:${id}`, user);
return user;
}
}
// 不规范:无依赖的类
class MathUtils {
add(a: number, b: number): number {
return a + b;
}
}
// 规范:普通函数
function add(a: number, b: number): number {
return a + b;
}Class Structure
类结构
Use constructor injection into private readonly fields.
typescript
// GOOD: constructor injection, private readonly
class OrderProcessor {
constructor(
private readonly orderRepo: OrderRepository,
private readonly paymentService: PaymentService,
private readonly notifier: NotificationService,
) {}
async processOrder(orderId: string): Promise<void> {
const order = await this.orderRepo.findById(orderId);
// implementation
}
}
// BAD: public mutable fields
class OrderProcessor {
public orderRepo: OrderRepository;
public paymentService: PaymentService;
constructor(orderRepo: OrderRepository, paymentService: PaymentService) {
this.orderRepo = orderRepo;
this.paymentService = paymentService;
}
}使用构造函数注入私有只读字段。
typescript
// 规范:构造函数注入,私有只读字段
class OrderProcessor {
constructor(
private readonly orderRepo: OrderRepository,
private readonly paymentService: PaymentService,
private readonly notifier: NotificationService,
) {}
async processOrder(orderId: string): Promise<void> {
const order = await this.orderRepo.findById(orderId);
// 实现代码
}
}
// 不规范:公共可变字段
class OrderProcessor {
public orderRepo: OrderRepository;
public paymentService: PaymentService;
constructor(orderRepo: OrderRepository, paymentService: PaymentService) {
this.orderRepo = orderRepo;
this.paymentService = paymentService;
}
}The 'this' Keyword
this关键字
Use only in class methods. Avoid elsewhere.
thistypescript
// GOOD: this in class method
class Counter {
private count = 0;
increment(): void {
this.count++;
}
}
// BAD: this in object literal
const counter = {
count: 0,
increment() {
this.count++; // fragile, breaks when passed as callback
},
};
// GOOD: closure over variable
function createCounter() {
let count = 0;
return {
increment: () => count++,
getCount: () => count,
};
}仅在类方法中使用,其他场景避免使用。
thistypescript
// 规范:类方法中使用this
class Counter {
private count = 0;
increment(): void {
this.count++;
}
}
// 不规范:对象字面量中使用this
const counter = {
count: 0,
increment() {
this.count++; // 脆弱,作为回调传递时会失效
},
};
// 规范:通过闭包捕获变量
function createCounter() {
let count = 0;
return {
increment: () => count++,
getCount: () => count,
};
}Type Inference
类型推断
When Inference is Acceptable
可接受的推断场景
Always explicit in function signatures. Infer in local variables, loops, destructuring, and intermediate calculations.
typescript
// GOOD: explicit function signature, inferred locals
function processUsers(users: ReadonlyArray<User>): Array<ProcessedUser> {
const results: Array<ProcessedUser> = [];
for (const user of users) { // user inferred as User
const name = user.name; // name inferred as string
const upper = name.toUpperCase(); // upper inferred as string
const processed = {id: user.id, name: upper}; // processed inferred
results.push(processed);
}
return results;
}
// GOOD: destructuring with inference
function formatUser({name, email}: User): string {
return `${name} <${email}>`;
}
// BAD: missing return type
function processUsers(users: ReadonlyArray<User>) {
// ...
}
// BAD: excessive annotations on locals
function processUsers(users: ReadonlyArray<User>): Array<ProcessedUser> {
const results: Array<ProcessedUser> = [];
for (const user: User of users) {
const name: string = user.name;
const upper: string = name.toUpperCase();
// ...
}
return results;
}函数签名始终显式声明类型,局部变量、循环、解构和中间计算可使用推断。
typescript
// 规范:函数签名显式,局部变量推断
function processUsers(users: ReadonlyArray<User>): Array<ProcessedUser> {
const results: Array<ProcessedUser> = [];
for (const user of users) { // user被推断为User类型
const name = user.name; // name被推断为string类型
const upper = name.toUpperCase(); // upper被推断为string类型
const processed = {id: user.id, name: upper}; // processed类型自动推断
results.push(processed);
}
return results;
}
// 规范:解构时使用推断
function formatUser({name, email}: User): string {
return `${name} <${email}>`;
}
// 不规范:缺失返回类型
function processUsers(users: ReadonlyArray<User>) {
// ...
}
// 不规范:局部变量过度注解
function processUsers(users: ReadonlyArray<User>): Array<ProcessedUser> {
const results: Array<ProcessedUser> = [];
for (const user: User of users) {
const name: string = user.name;
const upper: string = name.toUpperCase();
// ...
}
return results;
}Immutability
不可变性
Readonly by Default
默认只读
Mark reference type parameters as . Use for all bindings unless mutation needed.
Readonly<T>consttypescript
// GOOD: readonly parameters
function processData(
data: Readonly<UserData>,
config: Readonly<ProcessConfig>,
): ProcessResult {
// data and config cannot be mutated
return {success: true};
}
// GOOD: const bindings
function calculateTotal(items: ReadonlyArray<Item>): number {
const taxRate = 0.08;
const subtotal = items.reduce((sum, item) => sum + item.price, 0);
const tax = subtotal * taxRate;
return subtotal + tax;
}
// BAD: mutable parameters
function processData(data: UserData, config: ProcessConfig): ProcessResult {
data.processed = true; // mutation
return {success: true};
}引用类型参数标记为。所有绑定默认使用,除非需要修改。
Readonly<T>consttypescript
// 规范:参数只读
function processData(
data: Readonly<UserData>,
config: Readonly<ProcessConfig>,
): ProcessResult {
// data和config无法被修改
return {success: true};
}
// 规范:绑定使用const
function calculateTotal(items: ReadonlyArray<Item>): number {
const taxRate = 0.08;
const subtotal = items.reduce((sum, item) => sum + item.price, 0);
const tax = subtotal * taxRate;
return subtotal + tax;
}
// 不规范:可变参数
function processData(data: UserData, config: ProcessConfig): ProcessResult {
data.processed = true; // 修改参数
return {success: true};
}Arrays
数组
ALWAYS use or . NEVER use syntax.
Array<T>ReadonlyArray<T>T[]typescript
// GOOD: Array<T> syntax
const numbers: Array<number> = [1, 2, 3];
const roles: Array<UserRole> = ['admin', 'editor'];
function calculateAverage(numbers: ReadonlyArray<number>): number {
return numbers.reduce((a, b) => a + b, 0) / numbers.length;
}
// BAD: T[] syntax (don't use this even if common in examples)
const numbers: number[] = [1, 2, 3]; // NO
const roles: UserRole[] = ['admin']; // NO
function calculateAverage(numbers: number[]): number { // NO
// ...
}Why: Consistency with other generic syntax. is explicit and matches , , , etc. The syntax is muscle memory from other languages but inconsistent with TypeScript's generic patterns.
Array<T>ReadonlyArray<T>Record<K, V>Promise<T>T[]Prefer readonly outside local scope:
typescript
// GOOD: readonly array for function parameter
function calculateAverage(numbers: ReadonlyArray<number>): number {
return numbers.reduce((a, b) => a + b, 0) / numbers.length;
}
// GOOD: mutable array in local scope
function processItems(items: ReadonlyArray<Item>): Array<ProcessedItem> {
const results: Array<ProcessedItem> = [];
for (const item of items) {
results.push(transformItem(item));
}
return results;
}始终使用或,绝不使用语法。
Array<T>ReadonlyArray<T>T[]typescript
// 规范:Array<T>语法
const numbers: Array<number> = [1, 2, 3];
const roles: Array<UserRole> = ['admin', 'editor'];
function calculateAverage(numbers: ReadonlyArray<number>): number {
return numbers.reduce((a, b) => a + b, 0) / numbers.length;
}
// 不规范:T[]语法(即使示例中常见也不要使用)
const numbers: number[] = [1, 2, 3]; // 禁止
const roles: UserRole[] = ['admin']; // 禁止
function calculateAverage(numbers: number[]): number { // 禁止
// ...
}理由: 与其他泛型语法保持一致。更显式,且与、、等语法匹配。是其他语言的肌肉记忆,但与TypeScript的泛型模式不一致。
Array<T>ReadonlyArray<T>Record<K, V>Promise<T>T[]局部作用域外优先使用只读:
typescript
// 规范:函数参数使用只读数组
function calculateAverage(numbers: ReadonlyArray<number>): number {
return numbers.reduce((a, b) => a + b, 0) / numbers.length;
}
// 规范:局部作用域使用可变数组
function processItems(items: ReadonlyArray<Item>): Array<ProcessedItem> {
const results: Array<ProcessedItem> = [];
for (const item of items) {
results.push(transformItem(item));
}
return results;
}Deep Immutability
深度不可变性
Use for shallow immutability, from type-fest when you need immutability all the way down.
Readonly<T>ReadonlyDeep<T>typescript
import type {ReadonlyDeep} from 'type-fest';
// GOOD: shallow readonly for flat objects
type UserData = Readonly<{
id: string;
name: string;
email: string;
}>;
// GOOD: deep readonly for nested structures
type AppConfig = ReadonlyDeep<{
database: {
host: string;
port: number;
credentials: {
username: string;
password: string;
};
};
features: {
enabled: Array<string>;
};
}>;
function loadConfig(config: AppConfig): void {
// config is deeply immutable
// config.database.credentials.username = 'x'; // ERROR
}浅不可变性使用,需要深层不可变性时使用type-fest库的。
Readonly<T>ReadonlyDeep<T>typescript
import type {ReadonlyDeep} from 'type-fest';
// 规范:扁平对象使用浅只读
type UserData = Readonly<{
id: string;
name: string;
email: string;
}>;
// 规范:嵌套结构使用深度只读
type AppConfig = ReadonlyDeep<{
database: {
host: string;
port: number;
credentials: {
username: string;
password: string;
};
};
features: {
enabled: Array<string>;
};
}>;
function loadConfig(config: AppConfig): void {
// config是深度不可变的
// config.database.credentials.username = 'x'; // 报错
}Mathematics and Currency
数学与货币计算
When to Use math.js
何时使用math.js
ALWAYS use math.js for:
- Currency calculations (money)
- Financial calculations (interest, ROI, profit margins)
- Precision-critical percentages
- Complex mathematical operations requiring high precision
NEVER use JavaScript for:
number- Money / currency amounts
- Financial reporting calculations
- Any calculation where precision errors are unacceptable
typescript
import { create, all, MathJsInstance } from 'mathjs';
const math: MathJsInstance = create(all);
// GOOD: math.js for currency calculations
function calculateTotal(
price: number,
quantity: number,
taxRate: number
): string {
const subtotal = math.multiply(
math.bignumber(price),
math.bignumber(quantity)
);
const tax = math.multiply(subtotal, math.bignumber(taxRate));
const total = math.add(subtotal, tax);
return math.format(total, { precision: 14 });
}
// GOOD: math.js for financial calculations
function calculateROI(
initialInvestment: number,
finalValue: number
): string {
const initial = math.bignumber(initialInvestment);
const final = math.bignumber(finalValue);
const difference = math.subtract(final, initial);
const ratio = math.divide(difference, initial);
const percentage = math.multiply(ratio, 100);
return math.format(percentage, { precision: 14 });
}
// BAD: JavaScript number for currency
function calculateTotal(price: number, quantity: number, taxRate: number): number {
const subtotal = price * quantity; // NO: precision errors
const tax = subtotal * taxRate; // NO: compounding errors
return subtotal + tax; // NO: wrong for money
}
// BAD: JavaScript number for percentages in finance
function calculateDiscount(price: number, discountPercent: number): number {
return price * (discountPercent / 100); // NO: precision errors
}Why math.js:
- JavaScript's native uses IEEE 754 double-precision floating-point
number - This causes precision errors:
0.1 + 0.2 !== 0.3 - For financial calculations, these errors are unacceptable
- math.js BigNumber provides arbitrary precision arithmetic
When JavaScript number is OK:
- Counters and indices
- Simple integer math (within safe integer range)
- Display coordinates, dimensions
- Non-critical calculations where precision doesn't matter
以下场景必须使用math.js:
- 货币计算(金额)
- 金融计算(利息、投资回报率、利润率)
- 精度要求高的百分比计算
- 需要高精度的复杂数学运算
以下场景绝不能使用JavaScript原生类型:
number- 金额/货币数值
- 财务报表计算
- 任何精度错误不可接受的计算
typescript
import { create, all, MathJsInstance } from 'mathjs';
const math: MathJsInstance = create(all);
// 规范:货币计算使用math.js
function calculateTotal(
price: number,
quantity: number,
taxRate: number
): string {
const subtotal = math.multiply(
math.bignumber(price),
math.bignumber(quantity)
);
const tax = math.multiply(subtotal, math.bignumber(taxRate));
const total = math.add(subtotal, tax);
return math.format(total, { precision: 14 });
}
// 规范:金融计算使用math.js
function calculateROI(
initialInvestment: number,
finalValue: number
): string {
const initial = math.bignumber(initialInvestment);
const final = math.bignumber(finalValue);
const difference = math.subtract(final, initial);
const ratio = math.divide(difference, initial);
const percentage = math.multiply(ratio, 100);
return math.format(percentage, { precision: 14 });
}
// 不规范:用JavaScript number处理货币
function calculateTotal(price: number, quantity: number, taxRate: number): number {
const subtotal = price * quantity; // 禁止:精度错误
const tax = subtotal * taxRate; // 禁止:误差累积
return subtotal + tax; // 禁止:货币计算错误
}
// 不规范:用JavaScript number处理金融百分比
function calculateDiscount(price: number, discountPercent: number): number {
return price * (discountPercent / 100); // 禁止:精度错误
}为什么用math.js:
- JavaScript原生使用IEEE 754双精度浮点数
number - 会导致精度错误:
0.1 + 0.2 !== 0.3 - 金融计算中,这些错误是不可接受的
- math.js的BigNumber提供任意精度算术
JavaScript number可接受的场景:
- 计数器与索引
- 简单整数运算(在安全整数范围内)
- 显示坐标、尺寸
- 非关键计算,精度无关紧要
Nullability
空值处理
Null vs Undefined
Null vs Undefined
Use for absent values. means uninitialized. Proactively coalesce to null.
nullundefinedtypescript
// GOOD: null for absent, undefined for uninitialized
type User = {
name: string;
email: string;
phone: string | null; // may be absent
};
function findUser(id: string): User | null {
const user = database.users.get(id);
return user ?? null; // coalesce undefined to null
}
// GOOD: optional properties use ?:
type UserOptions = {
name: string;
email: string;
newsletter?: boolean; // may be undefined
};
// BAD: undefined for absent values
function findUser(id: string): User | undefined {
// prefer null for explicit absence
}
// GOOD: coalescing array access
const arr: Array<number> = [1, 2, 3];
const value: number | null = arr[10] ?? null;缺失值使用,表示未初始化。主动将值合并为null。
nullundefinedtypescript
// 规范:null表示缺失,undefined表示未初始化
type User = {
name: string;
email: string;
phone: string | null; // 可能缺失
};
function findUser(id: string): User | null {
const user = database.users.get(id);
return user ?? null; // 将undefined合并为null
}
// 规范:可选属性使用?:
type UserOptions = {
name: string;
email: string;
newsletter?: boolean; // 可能为undefined
};
// 不规范:用undefined表示缺失值
function findUser(id: string): User | undefined {
// 优先用null表示明确的缺失
}
// 规范:数组访问时合并空值
const arr: Array<number> = [1, 2, 3];
const value: number | null = arr[10] ?? null;Enums and Unions
枚举与联合类型
Prefer String Literal Unions
优先使用字符串字面量联合类型
Avoid enums. Use string literal unions instead.
typescript
// GOOD: string literal union
type Status = 'pending' | 'active' | 'complete' | 'failed';
function processStatus(status: Status): void {
switch (status) {
case 'pending':
// handle pending
break;
case 'active':
// handle active
break;
case 'complete':
// handle complete
break;
case 'failed':
// handle failed
break;
}
}
// BAD: enum
enum Status {
Pending = 'pending',
Active = 'active',
Complete = 'complete',
Failed = 'failed',
}Rationale: String literal unions are simpler, work better with discriminated unions, and don't generate runtime code.
避免使用枚举,改用字符串字面量联合类型。
typescript
// 规范:字符串字面量联合类型
type Status = 'pending' | 'active' | 'complete' | 'failed';
function processStatus(status: Status): void {
switch (status) {
case 'pending':
// 处理pending状态
break;
case 'active':
// 处理active状态
break;
case 'complete':
// 处理complete状态
break;
case 'failed':
// 处理failed状态
break;
}
}
// 不规范:枚举
enum Status {
Pending = 'pending',
Active = 'active',
Complete = 'complete',
Failed = 'failed',
}理由: 字符串字面量联合类型更简单,与可区分联合类型配合更好,且不会生成运行时代码。
Type Safety
类型安全
Never Use 'any'
绝不使用'any'
Always use for truly unknown data. If a library forces , escalate to operator for replacement.
unknownanytypescript
// GOOD: unknown with type guard
function parseJSON(json: string): unknown {
return JSON.parse(json);
}
function processData(json: string): User {
const data: unknown = parseJSON(json);
if (isUser(data)) {
return data;
}
throw new Error('Invalid user data');
}
function isUser(value: unknown): value is User {
return (
typeof value === 'object' &&
value !== null &&
'name' in value &&
'email' in value
);
}
// BAD: using any
function parseJSON(json: string): any {
return JSON.parse(json);
}真正未知的数据始终使用。如果库强制使用,请向负责人申请替换该库。
unknownanytypescript
// 规范:unknown配合类型守卫
function parseJSON(json: string): unknown {
return JSON.parse(json);
}
function processData(json: string): User {
const data: unknown = parseJSON(json);
if (isUser(data)) {
return data;
}
throw new Error('无效的用户数据');
}
function isUser(value: unknown): value is User {
return (
typeof value === 'object' &&
value !== null &&
'name' in value &&
'email' in value
);
}
// 不规范:使用any
function parseJSON(json: string): any {
return JSON.parse(json);
}Type Assertions
类型断言
Only for TypeScript system limitations. Always include comment explaining why.
typescript
// OK: DOM API limitation
const input = document.getElementById('email') as HTMLInputElement;
// DOM API returns HTMLElement, but we know it's an input
// OK: after runtime validation
const data: unknown = JSON.parse(jsonString);
if (isUser(data)) {
const user = data; // type guard narrows to User
}
// BAD: assertion without validation
const user = data as User; // no runtime check
// BAD: assertion to avoid type error
const value = (someValue as any) as TargetType;仅在TypeScript系统限制下使用,始终添加注释说明原因。
typescript
// 可接受:DOM API限制
const input = document.getElementById('email') as HTMLInputElement;
// DOM API返回HTMLElement,但我们知道它是input元素
// 可接受:运行时验证后
const data: unknown = JSON.parse(jsonString);
if (isUser(data)) {
const user = data; // 类型守卫将类型收窄为User
}
// 不规范:无验证的断言
const user = data as User; // 无运行时检查
// 不规范:为避免类型错误而断言
const value = (someValue as any) as TargetType;Non-null Assertion (!)
非空断言(!)
Same rules as type assertions - sparingly, with justification.
typescript
// OK: after explicit check
const user = users.find(u => u.id === targetId);
if (user) {
processUser(user); // user is non-null here, no need for !
}
// OK (with comment): known initialization pattern
class Service {
private connection!: Connection;
// connection initialized in async init() called by constructor
constructor() {
this.init();
}
private async init(): Promise<void> {
this.connection = await createConnection();
}
}
// BAD: hiding real potential null
const value = map.get(key)!; // what if key doesn't exist?与类型断言规则相同——谨慎使用,且需说明理由。
typescript
// 可接受:显式检查后
const user = users.find(u => u.id === targetId);
if (user) {
processUser(user); // 此处user非空,无需使用!
}
// 可接受(带注释):已知初始化模式
class Service {
private connection!: Connection;
// connection在构造函数调用的async init()中初始化
constructor() {
this.init();
}
private async init(): Promise<void> {
this.connection = await createConnection();
}
}
// 不规范:隐藏潜在的空值
const value = map.get(key)!; // 如果key不存在怎么办?Type Guards
类型守卫
Use type guards to narrow unknown types. Prefer built-in checks when possible.
typescript
// GOOD: typeof/instanceof for primitives/classes
function processValue(value: unknown): string {
if (typeof value === 'string') {
return value.toUpperCase();
}
if (typeof value === 'number') {
return value.toString();
}
throw new Error('Unsupported type');
}
// GOOD: custom type guard with 'is'
function isUser(value: unknown): value is User {
return (
typeof value === 'object' &&
value !== null &&
'name' in value &&
typeof (value as any).name === 'string' &&
'email' in value &&
typeof (value as any).email === 'string'
);
}
// GOOD: discriminated union
type Result =
| {type: 'success'; data: string}
| {type: 'error'; message: string};
function handleResult(result: Result): void {
if (result.type === 'success') {
console.log(result.data); // narrowed to success
} else {
console.error(result.message); // narrowed to error
}
}
// GOOD: schema validation (TypeBox preferred)
import {Type, Static} from '@sinclair/typebox';
const UserSchema = Type.Object({
name: Type.String(),
email: Type.String(),
age: Type.Number(),
});
type User = Static<typeof UserSchema>;
function validateUser(data: unknown): data is User {
return Value.Check(UserSchema, data);
}使用类型守卫收窄unknown类型,优先使用内置检查。
typescript
// 规范:对原始类型/类使用typeof/instanceof
function processValue(value: unknown): string {
if (typeof value === 'string') {
return value.toUpperCase();
}
if (typeof value === 'number') {
return value.toString();
}
throw new Error('不支持的类型');
}
// 规范:自定义带'is'的类型守卫
function isUser(value: unknown): value is User {
return (
typeof value === 'object' &&
value !== null &&
'name' in value &&
typeof (value as any).name === 'string' &&
'email' in value &&
typeof (value as any).email === 'string'
);
}
// 规范:可区分联合类型
type Result =
| {type: 'success'; data: string}
| {type: 'error'; message: string};
function handleResult(result: Result): void {
if (result.type === 'success') {
console.log(result.data); // 收窄为success类型
} else {
console.error(result.message); // 收窄为error类型
}
}
// 规范:Schema验证(优先使用TypeBox)
import {Type, Static} from '@sinclair/typebox';
const UserSchema = Type.Object({
name: Type.String(),
email: Type.String(),
age: Type.Number(),
});
type User = Static<typeof UserSchema>;
function validateUser(data: unknown): data is User {
return Value.Check(UserSchema, data);
}Generics
泛型
Generic Constraints
泛型约束
Always constrain generics when possible. Use descriptive names.
typescript
// GOOD: constrained with descriptive name
function mapItems<TItem, TResult>(
items: ReadonlyArray<TItem>,
mapper: (item: TItem) => TResult,
): Array<TResult> {
return items.map(mapper);
}
// GOOD: constraint on generic
function getProperty<TObj extends object, TKey extends keyof TObj>(
obj: TObj,
key: TKey,
): TObj[TKey] {
return obj[key];
}
// BAD: unconstrained, single-letter names
function getProperty<T, K>(obj: T, key: K): any {
return (obj as any)[key];
}尽可能约束泛型,使用描述性名称。
typescript
// 规范:带描述性名称的约束泛型
function mapItems<TItem, TResult>(
items: ReadonlyArray<TItem>,
mapper: (item: TItem) => TResult,
): Array<TResult> {
return items.map(mapper);
}
// 规范:泛型约束
function getProperty<TObj extends object, TKey extends keyof TObj>(
obj: TObj,
key: TKey,
): TObj[TKey] {
return obj[key];
}
// 不规范:无约束、单字母名称
function getProperty<T, K>(obj: T, key: K): any {
return (obj as any)[key];
}Avoid Over-Generalization
避免过度泛化
Don't make things generic unless multiple concrete types will use it.
typescript
// GOOD: specific types for single use case
function formatUser(user: User): string {
return `${user.name} <${user.email}>`;
}
// BAD: unnecessary generic
function format<T extends {name: string; email: string}>(item: T): string {
return `${item.name} <${item.email}>`;
}除非有多个具体类型会使用,否则不要将代码泛型化。
typescript
// 规范:单一场景使用具体类型
function formatUser(user: User): string {
return `${user.name} <${user.email}>`;
}
// 不规范:不必要的泛型
function format<T extends {name: string; email: string}>(item: T): string {
return `${item.name} <${item.email}>`;
}Utility Types
工具类型
Built-in vs type-fest
内置工具类型vs type-fest
Use built-in utilities when available. Use type-fest for deep operations and specialized needs.
typescript
// GOOD: built-in utilities
type PartialUser = Partial<User>;
type RequiredUser = Required<User>;
type UserKeys = keyof User;
type UserValues = User[keyof User];
// GOOD: type-fest for deep operations
import type {PartialDeep, RequiredDeep, ReadonlyDeep} from 'type-fest';
type DeepPartialConfig = PartialDeep<AppConfig>;
type DeepRequiredConfig = RequiredDeep<AppConfig>;优先使用内置工具类型,深度操作和特殊需求使用type-fest。
typescript
// 规范:使用内置工具类型
type PartialUser = Partial<User>;
type RequiredUser = Required<User>;
type UserKeys = keyof User;
type UserValues = User[keyof User];
// 规范:深度操作使用type-fest
import type {PartialDeep, RequiredDeep, ReadonlyDeep} from 'type-fest';
type DeepPartialConfig = PartialDeep<AppConfig>;
type DeepRequiredConfig = RequiredDeep<AppConfig>;Object Property Access
对象属性访问
Use for objects with dynamic keys.
Record<K, V>typescript
// GOOD: Record for dynamic keys
type UserCache = Record<string, User>;
function getUser(cache: UserCache, id: string): User | null {
return cache[id] ?? null;
}
// BAD: index signature
type UserCache = {
[key: string]: User;
};动态键的对象使用。
Record<K, V>typescript
// 规范:动态键使用Record
type UserCache = Record<string, User>;
function getUser(cache: UserCache, id: string): User | null {
return cache[id] ?? null;
}
// 不规范:索引签名
type UserCache = {
[key: string]: User;
};Derived Types
派生类型
Use mapped types for transformations. Create explicit types for complex derivations.
typescript
// GOOD: mapped type for simple transformation
type Nullable<T> = {
[K in keyof T]: T[K] | null;
};
type NullableUser = Nullable<User>;
// GOOD: explicit type for complex case
type UserUpdateData = {
name?: string;
email?: string;
// exclude id and other immutable fields explicitly
};
// BAD: overly clever utility type usage
type UserUpdateData = Omit<Partial<User>, 'id' | 'createdAt' | 'updatedAt'>;使用映射类型进行转换,复杂派生创建显式类型。
typescript
// 规范:简单转换使用映射类型
type Nullable<T> = {
[K in keyof T]: T[K] | null;
};
type NullableUser = Nullable<User>;
// 规范:复杂场景使用显式类型
type UserUpdateData = {
name?: string;
email?: string;
// 显式排除id和其他不可变字段
};
// 不规范:过度使用工具类型
type UserUpdateData = Omit<Partial<User>, 'id' | 'createdAt' | 'updatedAt'>;Module Organization
模块组织
Exports
导出
Use named exports only. No default exports.
typescript
// GOOD: named exports
export function processUser(user: User): ProcessedUser {
// implementation
}
export type ProcessedUser = {
id: string;
name: string;
};
// BAD: default export
export default function processUser(user: User): ProcessedUser {
// implementation
}仅使用具名导出,禁止默认导出。
typescript
// 规范:具名导出
export function processUser(user: User): ProcessedUser {
// 实现代码
}
export type ProcessedUser = {
id: string;
name: string;
};
// 不规范:默认导出
export default function processUser(user: User): ProcessedUser {
// 实现代码
}Barrel Exports
桶导出
Use index.ts to re-export from directories.
typescript
// src/users/index.ts
export * from './user-service';
export * from './user-repository';
export * from './types';
// consumers can import from directory
import {UserService, type User} from './users';使用index.ts重新导出目录内的模块。
typescript
// src/users/index.ts
export * from './user-service';
export * from './user-repository';
export * from './types';
// 消费者可从目录导入
import {UserService, type User} from './users';Import Organization
导入组织
Group by source type, alphabetize within groups. Use destructuring for fewer than 3 imports.
typescript
// GOOD: organized imports
// External dependencies
import {Result, ok, err} from 'neverthrow';
import type {ReadonlyDeep} from 'type-fest';
// Internal modules
import {DatabaseService} from '@/services/database';
import {Logger} from '@/services/logger';
// Relative imports
import {UserRepository} from './user-repository';
import type {User, UserData} from './types';
// GOOD: destructure for < 3 imports
import {foo, bar} from './utils';
// GOOD: namespace for 3+ imports
import * as utils from './utils';
utils.foo();
utils.bar();
utils.baz();Note: eslint-import plugin should be configured to enforce import ordering.
按来源类型分组,组内按字母排序。少于3个导入时使用解构。
typescript
// 规范:组织有序的导入
// 外部依赖
import {Result, ok, err} from 'neverthrow';
import type {ReadonlyDeep} from 'type-fest';
// 内部模块
import {DatabaseService} from '@/services/database';
import {Logger} from '@/services/logger';
// 相对导入
import {UserRepository} from './user-repository';
import type {User, UserData} from './types';
// 规范:<3个导入时使用解构
import {foo, bar} from './utils';
// 规范:≥3个导入时使用命名空间
import * as utils from './utils';
utils.foo();
utils.bar();
utils.baz();注意: 应配置eslint-import插件以强制导入顺序。
FCIS Integration
FCIS集成
Functional Core Patterns
函数式核心模式
Return Result types. Never throw exceptions. Pure functions only.
typescript
// pattern: Functional Core
import {Result, ok, err} from 'neverthrow';
type ValidationError = {
field: string;
message: string;
};
// GOOD: returns Result, pure function
function validateUser(
data: Readonly<UserData>,
): Result<ValidUser, ValidationError> {
if (!data.email) {
return err({field: 'email', message: 'Email required'});
}
if (!data.name) {
return err({field: 'name', message: 'Name required'});
}
return ok({...data, validated: true});
}
// GOOD: transformation with Result
function transformUser(
user: Readonly<User>,
config: Readonly<TransformConfig>,
): Result<TransformedUser, TransformError> {
// pure transformation logic
return ok(transformed);
}返回Result类型,绝不抛出异常,仅包含纯函数。
typescript
// 模式:函数式核心
import {Result, ok, err} from 'neverthrow';
type ValidationError = {
field: string;
message: string;
};
// 规范:返回Result,纯函数
function validateUser(
data: Readonly<UserData>,
): Result<ValidUser, ValidationError> {
if (!data.email) {
return err({field: 'email', message: '邮箱为必填项'});
}
if (!data.name) {
return err({field: 'name', message: '姓名为必填项'});
}
return ok({...data, validated: true});
}
// 规范:带Result的转换函数
function transformUser(
user: Readonly<User>,
config: Readonly<TransformConfig>,
): Result<TransformedUser, TransformError> {
// 纯转换逻辑
return ok(transformed);
}Imperative Shell Patterns
命令式外壳模式
May throw exceptions. Orchestrate I/O. Minimal business logic.
typescript
// pattern: Imperative Shell
import {HttpException} from './exceptions';
class UserController {
constructor(
private readonly userRepo: UserRepository,
private readonly logger: Logger,
) {}
// GOOD: orchestrates I/O, delegates to Core, may throw
async createUser(data: UserData): Promise<User> {
this.logger.info('Creating user', {email: data.email});
// Delegate validation to Functional Core
const validationResult = validateUser(data);
if (validationResult.isErr()) {
throw new HttpException(400, validationResult.error.message);
}
// I/O operation
const user = await this.userRepo.create(validationResult.value);
this.logger.info('User created', {id: user.id});
return user;
}
}可抛出异常,编排I/O操作,包含最少业务逻辑。
typescript
// 模式:命令式外壳
import {HttpException} from './exceptions';
class UserController {
constructor(
private readonly userRepo: UserRepository,
private readonly logger: Logger,
) {}
// 规范:编排I/O,委托给核心,可抛出异常
async createUser(data: UserData): Promise<User> {
this.logger.info('创建用户', {email: data.email});
// 委托验证给函数式核心
const validationResult = validateUser(data);
if (validationResult.isErr()) {
throw new HttpException(400, validationResult.error.message);
}
// I/O操作
const user = await this.userRepo.create(validationResult.value);
this.logger.info('用户创建成功', {id: user.id});
return user;
}
}Compiler Configuration
编译器配置
Strictness
严格模式
Full strict mode plus additional checks.
json
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true
}
}All strict options are mandatory. No exceptions.
启用完整严格模式及额外检查。
json
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true
}
}所有严格选项都是强制的,无例外。
Testing
测试
Test Type Safety
测试中的类型安全
Allow type assertions in tests for test data setup.
typescript
// OK in tests: type assertions for test data
const mockUser = {
id: '123',
name: 'Test User',
} as User;
// GOOD: factory functions
function createTestUser(overrides?: Partial<User>): User {
return {
id: '123',
name: 'Test User',
email: 'test@example.com',
...overrides,
};
}测试数据设置中允许使用类型断言。
typescript
// 测试中可接受:测试数据的类型断言
const mockUser = {
id: '123',
name: '测试用户',
} as User;
// 规范:使用工厂函数
function createTestUser(overrides?: Partial<User>): User {
return {
id: '123',
name: '测试用户',
email: 'test@example.com',
...overrides,
};
}Tools and Libraries
工具与库
Standard Stack
标准技术栈
- Type utilities: type-fest for deep operations and specialized utilities
- Validation: TypeBox preferred over zod (avoid decorator-based libraries)
- Result types: neverthrow for functional error handling
- Linting: eslint-import for import ordering
- 类型工具: type-fest 用于深度操作和特殊工具类型
- 验证: 优先使用TypeBox而非zod(避免基于装饰器的库)
- Result类型: neverthrow用于函数式错误处理
- 代码检查: eslint-import用于导入排序
Library Selection
库选择
When choosing between libraries, ALWAYS prefer the one without decorators.
typescript
// AVOID: decorator-based libraries
import {IsEmail, IsString} from 'class-validator';
class CreateUserDto {
@IsString()
name: string;
@IsEmail()
email: string;
}
// PREFER: schema-based validation
import {Type} from '@sinclair/typebox';
const CreateUserSchema = Type.Object({
name: Type.String(),
email: Type.String({format: 'email'}),
});选择库时,始终优先选择无装饰器的库。
typescript
// 避免:基于装饰器的库
import {IsEmail, IsString} from 'class-validator';
class CreateUserDto {
@IsString()
name: string;
@IsEmail()
email: string;
}
// 优先:基于Schema的验证
import {Type} from '@sinclair/typebox';
const CreateUserSchema = Type.Object({
name: Type.String(),
email: Type.String({format: 'email'}),
});Documentation
文档
JSDoc for Public APIs
公共API的JSDoc
Use JSDoc comments for exported functions and types.
typescript
/**
* Processes user data and returns a validated user object.
*
* @param data - Raw user data to process
* @returns Result containing validated user or validation error
*/
export function validateUser(
data: Readonly<UserData>,
): Result<ValidUser, ValidationError> {
// implementation
}
/**
* Configuration options for user processing.
*/
export type ProcessUserOptions = {
/** User's full name */
readonly name: string;
/** User's email address */
readonly email: string;
/** Whether to send welcome email (default: true) */
readonly sendWelcome?: boolean;
};导出的函数和类型使用JSDoc注释。
typescript
/**
* 处理用户数据并返回验证后的用户对象。
*
* @param data - 待处理的原始用户数据
* @returns 包含验证后用户或验证错误的Result
*/
export function validateUser(
data: Readonly<UserData>,
): Result<ValidUser, ValidationError> {
// 实现代码
}
/**
* 用户处理的配置选项。
*/
export type ProcessUserOptions = {
/** 用户全名 */
readonly name: string;
/** 用户邮箱地址 */
readonly email: string;
/** 是否发送欢迎邮件(默认:true) */
readonly sendWelcome?: boolean;
};Abstraction Guidelines
抽象指南
When to Abstract
何时抽象
Follow rule of three. Abstract when types become complex (3+ properties/levels).
typescript
// GOOD: abstract after third repetition
// First use
const user1 = {id: '1', name: 'Alice', email: 'alice@example.com'};
// Second use
const user2 = {id: '2', name: 'Bob', email: 'bob@example.com'};
// Third use - now abstract
type User = {
id: string;
name: string;
email: string;
};
// GOOD: abstract complex inline types
// Before
function process(data: {
user: {name: string; email: string};
settings: {theme: string; notifications: boolean};
}): void {}
// After - extract when > 3 properties or nested
type UserInfo = {
name: string;
email: string;
};
type UserSettings = {
theme: string;
notifications: boolean;
};
type ProcessData = {
user: UserInfo;
settings: UserSettings;
};
function process(data: Readonly<ProcessData>): void {}遵循三次原则。当类型变得复杂(≥3个属性/层级)时进行抽象。
typescript
// 规范:第三次重复时抽象
// 第一次使用
const user1 = {id: '1', name: 'Alice', email: 'alice@example.com'};
// 第二次使用
const user2 = {id: '2', name: 'Bob', email: 'bob@example.com'};
// 第三次使用 - 现在抽象
type User = {
id: string;
name: string;
email: string;
};
// 规范:复杂内联类型抽象
// 抽象前
function process(data: {
user: {name: string; email: string};
settings: {theme: string; notifications: boolean};
}): void {}
// 抽象后 - 属性>3个或嵌套时提取
type UserInfo = {
name: string;
email: string;
};
type UserSettings = {
theme: string;
notifications: boolean;
};
type ProcessData = {
user: UserInfo;
settings: UserSettings;
};
function process(data: Readonly<ProcessData>): void {}Sharp Edges
注意事项
Runtime hazards that TypeScript doesn't catch. Know these cold.
TypeScript无法捕获的运行时风险,必须牢记。
Equality
相等性
Always use . Never use .
=====typescript
// BAD: loose equality has surprising coercion
"0" == false; // true
[] == ![]; // true
null == undefined; // true
// GOOD: strict equality
"0" === false; // false
[] === ![]; // false
null === undefined; // falseTypeScript won't save you here—both are valid syntax.
始终使用,绝不使用。
=====typescript
// 不规范:松散相等会导致意外的类型转换
"0" == false; // true
[] == ![]; // true
null == undefined; // true
// 规范:严格相等
"0" === false; // false
[] === ![]; // false
null === undefined; // falseTypeScript无法帮你避免这个问题——两种语法都是合法的。
Prototype Pollution
原型污染
Never merge untrusted objects into plain objects.
typescript
// DANGEROUS: merging user input
const userInput = JSON.parse('{"__proto__": {"isAdmin": true}}');
Object.assign({}, userInput); // pollutes Object.prototype
// SAFE: use Map for dynamic keys from untrusted sources
const safeStore = new Map<string, unknown>();
safeStore.set(key, value);
// SAFE: null-prototype object
const safeObj = Object.create(null) as Record<string, unknown>;
// SAFE: validate keys before merge
function safeMerge<T extends object>(target: T, source: unknown): T {
if (typeof source !== 'object' || source === null) return target;
for (const key of Object.keys(source)) {
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
continue; // skip dangerous keys
}
(target as Record<string, unknown>)[key] = (source as Record<string, unknown>)[key];
}
return target;
}绝不要将不可信对象合并到普通对象中。
typescript
// 危险:合并用户输入
const userInput = JSON.parse('{"__proto__": {"isAdmin": true}}');
Object.assign({}, userInput); // 污染Object.prototype
// 安全:用Map存储不可信来源的动态键
const safeStore = new Map<string, unknown>();
safeStore.set(key, value);
// 安全:无原型对象
const safeObj = Object.create(null) as Record<string, unknown>;
// 安全:合并前验证键
function safeMerge<T extends object>(target: T, source: unknown): T {
if (typeof source !== 'object' || source === null) return target;
for (const key of Object.keys(source)) {
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
continue; // 跳过危险键
}
(target as Record<string, unknown>)[key] = (source as Record<string, unknown>)[key];
}
return target;
}Regular Expression DoS (ReDoS)
正则表达式拒绝服务(ReDoS)
Avoid nested quantifiers and overlapping alternatives.
typescript
// DANGEROUS: catastrophic backtracking
const bad1 = /(a+)+$/; // nested quantifiers
const bad2 = /(a|a)+$/; // overlapping alternatives
const bad3 = /(\w+)*$/; // greedy quantifier in group with quantifier
// These can freeze the event loop on crafted input like "aaaaaaaaaaaaaaaaaaaaaaaa!"
// SAFER: avoid nesting, use possessive-like patterns
const safer = /a+$/; // no nesting
const safest = /^[a-z]+$/; // anchored, simple character classWhen accepting user-provided regex patterns, use a timeout or run in a worker.
避免嵌套量词和重叠的备选分支。
typescript
// 危险:灾难性回溯
const bad1 = /(a+)+$/; // 嵌套量词
const bad2 = /(a|a)+$/; // 重叠备选分支
const bad3 = /(\w+)*$/; // 组内的贪婪量词
// 这些正则表达式在处理精心构造的输入(如"aaaaaaaaaaaaaaaaaaaaaaaa!")时会阻塞事件循环
// 更安全:避免嵌套,使用类似独占量词的模式
const safer = /a+$/; // 无嵌套
const safest = /^[a-z]+$/; // 锚定,简单字符类当接受用户提供的正则表达式时,使用超时机制或在worker中运行。
parseInt Radix
parseInt基数
Always specify the radix parameter.
typescript
// BAD: radix varies by engine/input
parseInt("08"); // 0 or 8 depending on engine
parseInt("0x10"); // 16 (hex prefix always recognized)
// GOOD: explicit radix
parseInt("08", 10); // 8
parseInt("10", 16); // 16
parseInt("1010", 2); // 10
// BETTER: use Number() for decimal
Number("08"); // 8
Number.parseInt("08", 10); // 8始终指定基数参数。
typescript
// 不规范:基数因引擎/输入而异
parseInt("08"); // 0或8,取决于引擎
parseInt("0x10"); // 16(始终识别十六进制前缀)
// 规范:显式指定基数
parseInt("08", 10); // 8
parseInt("10", 16); // 16
parseInt("1010", 2); // 10
// 更好:十进制转换用Number()
Number("08"); // 8
Number.parseInt("08", 10); // 8Array Mutations
数组修改
Know which methods mutate in place.
| Mutates | Returns new array |
|---|---|
| |
| |
| |
| |
| spread: |
| - |
typescript
// BAD: mutates original
const original = [3, 1, 2];
const sorted = original.sort(); // original is now [1, 2, 3]
// GOOD: copy first (pre-ES2023)
const sorted = [...original].sort();
const sorted = original.slice().sort();
// GOOD: use non-mutating methods (ES2023+)
const sorted = original.toSorted();
const reversed = original.toReversed();了解哪些方法会原地修改数组。
| 原地修改 | 返回新数组 |
|---|---|
| |
| |
| |
| |
| 展开语法: |
| - |
typescript
// 不规范:修改原数组
const original = [3, 1, 2];
const sorted = original.sort(); // 原数组变为[1, 2, 3]
// 规范:先复制(ES2023之前)
const sorted = [...original].sort();
const sorted = original.slice().sort();
// 规范:使用非修改方法(ES2023+)
const sorted = original.toSorted();
const reversed = original.toReversed();Numeric Sort
数值排序
Default sort is lexicographic, not numeric.
typescript
// WRONG: sorts as strings
[10, 2, 1].sort(); // [1, 10, 2]
// CORRECT: numeric comparator
[10, 2, 1].sort((a, b) => a - b); // [1, 2, 10]
// Descending
[10, 2, 1].sort((a, b) => b - a); // [10, 2, 1]默认排序是字典序,而非数值序。
typescript
// 错误:按字符串排序
[10, 2, 1].sort(); // [1, 10, 2]
// 正确:数值比较器
[10, 2, 1].sort((a, b) => a - b); // [1, 2, 10]
// 降序
[10, 2, 1].sort((a, b) => b - a); // [10, 2, 1]eval and Function Constructor
eval与Function构造函数
Never use eval() or new Function() with untrusted input.
typescript
// DANGEROUS: code injection
eval(userInput); // arbitrary code execution
new Function('return ' + userInput)(); // same risk
// If you need dynamic evaluation, use a sandboxed environment or parser绝不要使用eval()或new Function()处理不可信输入。
typescript
// 危险:代码注入
eval(userInput); // 任意代码执行
new Function('return ' + userInput)(); // 同样风险
// 如果需要动态求值,使用沙箱环境或解析器JSON Precision Loss
JSON精度丢失
JSON.parse loses precision for large integers and BigInt.
typescript
// PROBLEM: JavaScript numbers lose precision > 2^53
JSON.parse('{"id": 9007199254740993}'); // id becomes 9007199254740992
// PROBLEM: BigInt not supported
JSON.parse('{"value": 123n}'); // SyntaxError
// SOLUTION: use string representation for large IDs
type ApiResponse = {
id: string; // "9007199254740993" - keep as string
};
// SOLUTION: use a BigInt-aware parser for financial data
// Or use string fields and parse with BigInt() afterJSON.parse会丢失大整数和BigInt的精度。
typescript
// 问题:JavaScript数字在>2^53时丢失精度
JSON.parse('{"id": 9007199254740993}'); // id变为9007199254740992
// 问题:不支持BigInt
JSON.parse('{"value": 123n}'); // 语法错误
// 解决方案:大ID使用字符串表示
type ApiResponse = {
id: string; // "9007199254740993" - 保持为字符串
};
// 解决方案:金融数据使用支持BigInt的解析器
// 或使用字符串字段,之后用BigInt()解析Promise.all vs Promise.allSettled
Promise.all vs Promise.allSettled
Promise.all fails fast; Promise.allSettled waits for all.
typescript
// Promise.all: rejects immediately on first failure
// Use when: all must succeed, fail fast is desired
async function fetchAllRequired(ids: ReadonlyArray<string>): Promise<Array<User>> {
const promises = ids.map(id => fetchUser(id));
return Promise.all(promises); // throws on first failure
}
// Promise.allSettled: waits for all, never rejects
// Use when: need results from successful ones even if some fail
async function fetchAllBestEffort(
ids: ReadonlyArray<string>,
): Promise<Array<User>> {
const promises = ids.map(id => fetchUser(id));
const results = await Promise.allSettled(promises);
return results
.filter((r): r is PromiseFulfilledResult<User> => r.status === 'fulfilled')
.map(r => r.value);
}
// Common patterns with allSettled
const results = await Promise.allSettled(promises);
const succeeded = results.filter(r => r.status === 'fulfilled');
const failed = results.filter(r => r.status === 'rejected');
// Log failures, return successes
for (const failure of failed) {
if (failure.status === 'rejected') {
logger.error('Operation failed', {reason: failure.reason});
}
}| Method | Behavior | Use when |
|---|---|---|
| Rejects on first failure | All must succeed |
| Always resolves with status array | Need partial results |
| Resolves/rejects with first to complete | Timeout patterns |
| Resolves with first success, rejects if all fail | First success wins |
Promise.all快速失败;Promise.allSettled等待所有操作完成。
typescript
// Promise.all:第一个失败时立即拒绝
// 使用场景:所有操作必须成功,需要快速失败
async function fetchAllRequired(ids: ReadonlyArray<string>): Promise<Array<User>> {
const promises = ids.map(id => fetchUser(id));
return Promise.all(promises); // 第一个失败时抛出
}
// Promise.allSettled:等待所有操作完成,从不拒绝
// 使用场景:即使部分失败,也需要成功的结果
async function fetchAllBestEffort(
ids: ReadonlyArray<string>,
): Promise<Array<User>> {
const promises = ids.map(id => fetchUser(id));
const results = await Promise.allSettled(promises);
return results
.filter((r): r is PromiseFulfilledResult<User> => r.status === 'fulfilled')
.map(r => r.value);
}
// allSettled常见模式
const results = await Promise.allSettled(promises);
const succeeded = results.filter(r => r.status === 'fulfilled');
const failed = results.filter(r => r.status === 'rejected');
// 记录失败,返回成功结果
for (const failure of failed) {
if (failure.status === 'rejected') {
logger.error('操作失败', {reason: failure.reason});
}
}| 方法 | 行为 | 使用场景 |
|---|---|---|
| 第一个失败时拒绝 | 所有操作必须成功 |
| 始终解析为状态数组 | 需要部分结果 |
| 第一个完成时解析/拒绝 | 超时模式 |
| 第一个成功时解析,全部失败时拒绝 | 第一个成功即可 |
Unsafe Property Access
不安全的属性访问
Bracket notation with user input is dangerous.
typescript
// DANGEROUS: arbitrary property access
function getValue(obj: object, key: string): unknown {
return (obj as Record<string, unknown>)[key]; // could access __proto__, constructor
}
// SAFER: validate or use Map
function safeGetValue(obj: Record<string, unknown>, key: string): unknown {
if (!Object.hasOwn(obj, key)) return undefined;
if (key === '__proto__' || key === 'constructor') return undefined;
return obj[key];
}使用括号语法访问用户输入的键是危险的。
typescript
// 危险:任意属性访问
function getValue(obj: object, key: string): unknown {
return (obj as Record<string, unknown>)[key]; // 可能访问__proto__、constructor
}
// 更安全:验证或使用Map
function safeGetValue(obj: Record<string, unknown>, key: string): unknown {
if (!Object.hasOwn(obj, key)) return undefined;
if (key === '__proto__' || key === 'constructor') return undefined;
return obj[key];
}Common Mistakes
常见错误
| Mistake | Fix |
|---|---|
Using | Use |
Using | Use |
| Use |
| Type assertions without validation | Add runtime validation or type guard |
| Mutable parameters | Mark as |
| Use |
| Enums | Use string literal unions |
| Missing return types on exports | Always type function returns |
Using | Use |
JavaScript | Use math.js with BigNumber |
| Decorators (unless framework requires) | Use functions or type-based solutions |
| Default exports | Use named exports only |
| Over-abstraction before third use | Wait for pattern to emerge |
| Title Case error messages | Use lowercase fragments: |
| Unnecessary async on pure functions | Keep sync unless I/O is involved |
| Use |
| Use |
| Use |
| Validate keys or use |
Nested regex quantifiers | Refactor to avoid ReDoS |
| Use |
| 错误 | 修复方案 |
|---|---|
用 | 改用 |
业务逻辑中使用 | 改用 |
顶层函数用 | 改用 |
| 无验证的类型断言 | 添加运行时验证或类型守卫 |
| 可变参数 | 引用类型参数标记为 |
用 | 改用 |
| 使用枚举 | 改用字符串字面量联合类型 |
| 导出函数缺失返回类型 | 始终声明函数返回类型 |
用 | 改用 |
用JavaScript | 用math.js的BigNumber |
| 使用装饰器(除非框架要求) | 改用函数或基于类型的方案 |
| 使用默认导出 | 仅使用具名导出 |
| 第三次使用前就抽象 | 等待模式出现后再抽象 |
| 错误消息使用标题大小写 | 使用小写片段: |
| 纯函数不必要地使用异步 | 保持同步,除非涉及I/O |
用 | 始终用 |
| 用 |
数值数组用 | 用 |
用 | 验证键或使用 |
正则表达式使用嵌套量词 | 重构以避免ReDoS |
可接受部分结果时用 | 改用 |
Red Flags
危险信号
STOP and refactor when you see:
- keyword in business logic
any - for data shapes (not class contracts)
interface - JavaScript for money, currency, or financial calculations
number - instead of
T[]syntaxArray<T> - Decorators in library selection
- Type assertions without explanatory comments
- Missing return types on exported functions
- Mutable class fields (should be )
readonly - used for explicitly absent values
undefined - Enums instead of string literal unions
- Default exports
- Functions with 4+ positional parameters
- Complex inline types used repeatedly
- Async functions that don't perform I/O
- Error messages in Title Case
- instead of
===== - or
eval()with any dynamic inputnew Function() - Regex patterns with nested quantifiers or
(x+)+(x|x)+ - or spread with user-controlled objects
Object.assign() - without explicit radix
parseInt() - on numbers without comparator function
.sort() - on data with large integer IDs (use string IDs)
JSON.parse()
看到以下内容时,立即停止并重构:
- 业务逻辑中出现关键字
any - 用定义数据结构(而非类契约)
interface - 用JavaScript 处理货币、金融计算
number - 用而非
T[]语法Array<T> - 选择带装饰器的库
- 无注释说明的类型断言
- 导出函数缺失返回类型
- 可变类字段(应设为)
readonly - 用表示明确的缺失值
undefined - 用枚举而非字符串字面量联合类型
- 使用默认导出
- 函数有4个以上位置参数
- 复杂内联类型重复使用
- 未执行I/O的异步函数
- 标题大小写的错误消息
- 用而非
===== - 用或
eval()处理动态输入new Function() - 正则表达式有嵌套量词或
(x+)+(x|x)+ - 用或展开语法处理用户控制的对象
Object.assign() - 未指定基数
parseInt() - 数值数组用但未传比较器
.sort() - 用处理含大整数ID的数据(改用字符串ID)
JSON.parse()
Reference
参考
For comprehensive type-fest utilities documentation, see type-fest.md.
For comprehensive TypeBox validator documentation, see typebox.md. Please note that we generally use AJV as the canonical validator, but TypeBox is the schema generator.
完整的type-fest工具类型文档,请查看type-fest.md。
完整的TypeBox验证器文档,请查看typebox.md。请注意,我们通常使用AJV作为标准验证器,但TypeBox作为Schema生成器。