typescript-type-safety
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTypeScript Type Safety
TypeScript 类型安全
Overview
概述
Zero tolerance for types. Every is a runtime bug waiting to happen.
anyanyReplace with proper types using interfaces, with type guards, or generic constraints. Use with explanation only when absolutely necessary.
anyunknown@ts-expect-error对类型零容忍。 每个都是一个随时可能爆发的运行时bug。
anyany使用接口、搭配类型守卫的或泛型约束来替换。只有在绝对必要时,才配合说明使用。
unknownany@ts-expect-errorWhen to Use
适用场景
Use when you see:
- in function parameters or return types
: any - type assertions
as any - TypeScript errors you're tempted to ignore
- External libraries without proper types
- Catch blocks with implicit
any
Don't use for:
- Already properly typed code
- Third-party files (contribute upstream instead)
.d.ts
当你遇到以下情况时使用:
- 函数参数或返回类型中的
: any - 类型断言
as any - 你想忽略的TypeScript错误
- 没有合理类型定义的外部库
- 隐式的catch块
any
不适用场景:
- 已具备合理类型定义的代码
- 第三方文件(应向上游贡献修复)
.d.ts
Type Safety Hierarchy
类型安全层级
Prefer in this order:
- Explicit interface/type definition
- Generic type parameters with constraints
- Union types
- (with type guards)
unknown - (for impossible states)
never
Never use:
any优先顺序如下:
- 显式接口/类型定义
- 带约束的泛型类型参数
- 联合类型
- (搭配类型守卫)
unknown - (用于不可能的状态)
never
绝对不要使用:
anyQuick Reference
速查参考
| Pattern | Bad | Good |
|---|---|---|
| Error handling | | |
| Unknown data | | |
| Type assertions | | |
| Double casting | | Align interfaces instead: make types compatible |
| External libs | | |
| Generics | | |
| 模式 | 错误示例 | 正确示例 |
|---|---|---|
| 错误处理 | | |
| 未知数据 | | |
| 类型断言 | | |
| 双重类型转换 | | 对齐接口:让类型兼容 |
| 外部库 | | |
| 泛型 | | |
Implementation
实践方案
Error Handling
错误处理
typescript
// ❌ BAD
try {
await operation();
} catch (error: any) {
console.error(error.message);
}
// ✅ GOOD - Use unknown and type guard
try {
await operation();
} catch (error) {
if (error instanceof Error) {
console.error(error.message);
} else {
console.error('Unknown error:', String(error));
}
}
// ✅ BETTER - Helper function
function toError(error: unknown): Error {
if (error instanceof Error) return error;
return new Error(String(error));
}
try {
await operation();
} catch (error) {
const err = toError(error);
console.error(err.message);
}typescript
// ❌ 错误示例
try {
await operation();
} catch (error: any) {
console.error(error.message);
}
// ✅ 正确示例 - 使用unknown和类型守卫
try {
await operation();
} catch (error) {
if (error instanceof Error) {
console.error(error.message);
} else {
console.error('未知错误:', String(error));
}
}
// ✅ 更优方案 - 辅助函数
function toError(error: unknown): Error {
if (error instanceof Error) return error;
return new Error(String(error));
}
try {
await operation();
} catch (error) {
const err = toError(error);
console.error(err.message);
}Unknown Data Validation
未知数据校验
typescript
// ❌ BAD
const data = await response.json() as any;
console.log(data.user.name);
// ✅ GOOD - Type guard
interface UserResponse {
user: {
name: string;
email: string;
};
}
function isUserResponse(data: unknown): data is UserResponse {
return (
typeof data === 'object' &&
data !== null &&
'user' in data &&
typeof data.user === 'object' &&
data.user !== null &&
'name' in data.user &&
typeof data.user.name === 'string'
);
}
const data = await response.json();
if (isUserResponse(data)) {
console.log(data.user.name); // Type-safe
}typescript
// ❌ 错误示例
const data = await response.json() as any;
console.log(data.user.name);
// ✅ 正确示例 - 类型守卫
interface UserResponse {
user: {
name: string;
email: string;
};
}
function isUserResponse(data: unknown): data is UserResponse {
return (
typeof data === 'object' &&
data !== null &&
'user' in data &&
typeof data.user === 'object' &&
data.user !== null &&
'name' in data.user &&
typeof data.user.name === 'string'
);
}
const data = await response.json();
if (isUserResponse(data)) {
console.log(data.user.name); // 类型安全
}Module Augmentation
模块扩展
typescript
// ❌ BAD
const user = (request as any).user;
const db = (server as any).pg;
// ✅ GOOD - Augment third-party types
import { FastifyRequest, FastifyInstance } from 'fastify';
interface AuthUser {
user_id: string;
username: string;
email: string;
}
declare module 'fastify' {
interface FastifyRequest {
user?: AuthUser;
}
interface FastifyInstance {
pg: PostgresPlugin;
}
}
// Now type-safe everywhere
const user = request.user; // AuthUser | undefined
const db = server.pg; // PostgresPlugintypescript
// ❌ 错误示例
const user = (request as any).user;
const db = (server as any).pg;
// ✅ 正确示例 - 扩展第三方类型
import { FastifyRequest, FastifyInstance } from 'fastify';
interface AuthUser {
user_id: string;
username: string;
email: string;
}
declare module 'fastify' {
interface FastifyRequest {
user?: AuthUser;
}
interface FastifyInstance {
pg: PostgresPlugin;
}
}
// 现在所有地方都类型安全
const user = request.user; // AuthUser | undefined
const db = server.pg; // PostgresPluginGeneric Constraints
泛型约束
typescript
// ❌ BAD
function merge(a: any, b: any): any {
return { ...a, ...b };
}
// ✅ GOOD - Constrained generic
function merge<
T extends Record<string, unknown>,
U extends Record<string, unknown>
>(a: T, b: U): T & U {
return { ...a, ...b };
}typescript
// ❌ 错误示例
function merge(a: any, b: any): any {
return { ...a, ...b };
}
// ✅ 正确示例 - 带约束的泛型
function merge<
T extends Record<string, unknown>,
U extends Record<string, unknown>
>(a: T, b: U): T & U {
return { ...a, ...b };
}Type Alignment (Avoid Double Casts)
类型对齐(避免双重类型转换)
typescript
// ❌ BAD - Double cast indicates misaligned types
interface SearchPackage {
id: string;
type: string; // Too loose
}
interface RegistryPackage {
id: string;
type: PackageType; // Specific enum
}
return data.packages as unknown as RegistryPackage[]; // Hiding incompatibility
// ✅ GOOD - Align types from the source
interface SearchPackage {
id: string;
type: PackageType; // Use same specific type
}
interface RegistryPackage {
id: string;
type: PackageType; // Now compatible
}
return data.packages; // No cast needed - types matchRule: If you need , your interfaces are misaligned. Fix the root cause, don't hide it with double casts.
as unknown as Typetypescript
// ❌ 错误示例 - 双重类型转换表明类型不匹配
interface SearchPackage {
id: string;
type: string; // 过于松散
}
interface RegistryPackage {
id: string;
type: PackageType; // 特定枚举
}
return data.packages as unknown as RegistryPackage[]; // 隐藏不兼容性
// ✅ 正确示例 - 从源头对齐类型
interface SearchPackage {
id: string;
type: PackageType; // 使用相同的特定类型
}
interface RegistryPackage {
id: string;
type: PackageType; // 现在类型兼容
}
return data.packages; // 无需类型转换 - 类型匹配规则: 如果你需要使用,说明你的接口定义不匹配。修复根本问题,不要用双重类型转换来掩盖。
as unknown as TypeESM Import Extensions
ESM 导入扩展名
Always use extension for relative imports in ESM projects.
.jsNode.js ESM requires explicit file extensions. TypeScript compiles → , so imports must reference the output extension.
.ts.jstypescript
// ❌ BAD - Will fail at runtime in ESM
import { helper } from './utils';
import { CLIError } from '../utils/cli-error';
import type { Package } from './types/package';
// ✅ GOOD - Explicit .js extensions
import { helper } from './utils.js';
import { CLIError } from '../utils/cli-error.js';
import type { Package } from './types/package.js';Why this is a TypeScript/type safety issue:
- TypeScript doesn't catch missing extensions at compile time
- Errors only appear at runtime:
ERR_MODULE_NOT_FOUND - CI builds fail but local development works (cached modules)
- This is one of the most common "works locally, fails in CI" issues
TSConfig for ESM:
json
{
"compilerOptions": {
"module": "NodeNext",
"moduleResolution": "NodeNext",
// OR
"module": "ESNext",
"moduleResolution": "bundler"
}
}Common Import Mistakes:
| Pattern | Issue | Fix |
|---|---|---|
| Missing extension | |
| Missing index | |
| Package export | Check package.json |
Linting for Import Extensions:
bash
undefined在ESM项目中,相对导入必须始终使用扩展名。
.jsNode.js ESM要求显式的文件扩展名。TypeScript会将编译为,因此导入必须引用输出文件的扩展名。
.ts.jstypescript
// ❌ 错误示例 - 在ESM运行时会失败
import { helper } from './utils';
import { CLIError } from '../utils/cli-error';
import type { Package } from './types/package';
// ✅ 正确示例 - 显式使用.js扩展名
import { helper } from './utils.js';
import { CLIError } from '../utils/cli-error.js';
import type { Package } from './types/package.js';这为何是TypeScript/类型安全问题:
- TypeScript在编译时不会捕获缺失的扩展名
- 错误仅在运行时出现:
ERR_MODULE_NOT_FOUND - CI构建失败但本地开发正常(缓存模块)
- 这是最常见的“本地正常,CI失败”问题之一
ESM 对应的TSConfig配置:
json
{
"compilerOptions": {
"module": "NodeNext",
"moduleResolution": "NodeNext",
// 或者
"module": "ESNext",
"moduleResolution": "bundler"
}
}常见导入错误:
| 模式 | 问题 | 修复方案 |
|---|---|---|
| 缺失扩展名 | |
| 缺失index文件 | |
| 包导出问题 | 检查package.json的 |
导入扩展名的 lint 检查:
bash
undefinedFind imports missing .js extension
查找缺失.js扩展名的导入
grep -rn "from '..?/[^'][^j][^s]'" --include=".ts" src/
grep -rn "from '..?/[^'][^j][^s]'" --include=".ts" src/
ESLint rule (if using eslint)
ESLint规则(如果使用eslint)
"import/extensions": ["error", "always", { "ignorePackages": true }]
"import/extensions": ["error", "always", { "ignorePackages": true }]
undefinedundefinedCommon Mistakes
常见错误
| Mistake | Why It Fails | Fix |
|---|---|---|
Using | Loses all type safety | Use module augmentation or |
| Hides real type errors | Create proper interface or use |
| Misaligned interfaces | Align types at source - same enums/unions |
| Skipping catch block types | Unsafe error access | Use |
| Generic functions without constraints | Allows invalid operations | Add |
Ignoring | Tech debt compounds | Fix root cause, use |
Missing | ESM runtime failures | Always use |
| 错误做法 | 失败原因 | 修复方案 |
|---|---|---|
对第三方库使用 | 完全丧失类型安全 | 使用模块扩展或 |
对复杂类型使用 | 隐藏真实的类型错误 | 创建合理的接口或使用 |
| 接口定义不匹配 | 从源头对齐类型——使用相同的枚举/联合类型 |
| 忽略catch块的类型 | 不安全的错误访问 | 使用 |
| 无约束的泛型函数 | 允许无效操作 | 添加 |
忽略 | 技术债务不断增加 | 修复根本问题,使用带注释的 |
| 缺失.js导入扩展名 | ESM运行时失败 | 相对导入始终使用.js |
TSConfig Strict Settings
TSConfig 严格模式设置
Enable all strict options for maximum type safety:
json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
}
}启用所有严格选项以获得最大类型安全:
json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
}
}Type Audit Workflow
类型审计流程
- Find:
grep -r ": any\|as any" --include="*.ts" src/ - Categorize: Group by pattern (errors, requests, external libs)
- Define: Create interfaces/types for each category
- Replace: Systematic replacement with proper types
- Validate: must succeed
npm run build - Test: All tests must pass
- 查找:
grep -r ": any\|as any" --include="*.ts" src/ - 分类:按模式分组(错误处理、请求、外部库等)
- 定义:为每个分类创建接口/类型
- 替换:系统地用合理类型替换
- 验证:必须成功
npm run build - 测试:所有测试必须通过
Real-World Impact
实际业务影响
Before type safety:
- Runtime errors from undefined properties
- Silent failures from type mismatches
- Hours debugging production issues
- Difficult refactoring
After type safety:
- Errors caught at compile time
- IntelliSense shows all available properties
- Confident refactoring with compiler help
- Self-documenting code
Remember: Type safety isn't about making TypeScript happy - it's about preventing runtime bugs. Every you eliminate is a production bug you prevent.
any类型安全优化前:
- 未定义属性导致的运行时错误
- 类型不匹配导致的静默失败
- 花费数小时调试生产问题
- 重构困难
类型安全优化后:
- 编译时捕获错误
- 智能提示显示所有可用属性
- 在编译器的帮助下放心重构
- 代码自文档化
谨记: 类型安全不是为了让TypeScript满意——而是为了预防运行时bug。你消除的每一个,都是在预防一个生产环境中的bug。
any