typescript-strict-migrator
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTypeScript Strict Migrator
TypeScript 严格模式迁移指南
Incrementally migrate to TypeScript strict mode for maximum type safety.
逐步迁移至TypeScript严格模式,以实现最高级别的类型安全。
Core Workflow
核心工作流程
- Audit current state: Check existing type errors
- Enable incrementally: One flag at a time
- Fix errors: Systematic approach per flag
- Add type guards: Runtime type checking
- Use utility types: Proper type transformations
- Document patterns: Team guidelines
- 审计当前状态:检查现有类型错误
- 逐步启用:每次启用一个标志
- 修复错误:针对每个标志采用系统化方案
- 添加类型守卫:运行时类型检查
- 使用工具类型:正确的类型转换
- 记录模式:团队指南
Strict Mode Flags
严格模式标志
json
// tsconfig.json - Full strict mode
{
"compilerOptions": {
// Master flag (enables all below)
"strict": true,
// Individual flags (enabled by strict)
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"useUnknownInCatchVariables": true,
"alwaysStrict": true,
// Additional strict-ish flags (not in strict)
"noUncheckedIndexedAccess": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,
"exactOptionalPropertyTypes": true,
"noPropertyAccessFromIndexSignature": true
}
}json
// tsconfig.json - Full strict mode
{
"compilerOptions": {
// Master flag (enables all below)
"strict": true,
// Individual flags (enabled by strict)
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"useUnknownInCatchVariables": true,
"alwaysStrict": true,
// Additional strict-ish flags (not in strict)
"noUncheckedIndexedAccess": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,
"exactOptionalPropertyTypes": true,
"noPropertyAccessFromIndexSignature": true
}
}Incremental Migration Strategy
渐进式迁移策略
Phase 1: Basic Strict Flags
阶段1:基础严格模式标志
json
// tsconfig.json - Phase 1
{
"compilerOptions": {
"strict": false,
"noImplicitAny": true,
"alwaysStrict": true
}
}typescript
// Before: implicit any
function processData(data) {
return data.map(item => item.value);
}
// After: explicit types
function processData(data: DataItem[]): number[] {
return data.map(item => item.value);
}
interface DataItem {
value: number;
label: string;
}json
// tsconfig.json - Phase 1
{
"compilerOptions": {
"strict": false,
"noImplicitAny": true,
"alwaysStrict": true
}
}typescript
// 之前:隐式any类型
function processData(data) {
return data.map(item => item.value);
}
// 之后:显式类型
function processData(data: DataItem[]): number[] {
return data.map(item => item.value);
}
interface DataItem {
value: number;
label: string;
}Phase 2: Strict Null Checks
阶段2:严格空值检查
json
// tsconfig.json - Phase 2
{
"compilerOptions": {
"noImplicitAny": true,
"strictNullChecks": true
}
}typescript
// Before: potential null errors
function getUserName(user: User) {
return user.profile.name; // Error if profile is undefined
}
// After: proper null handling
function getUserName(user: User): string | undefined {
return user.profile?.name;
}
// With non-null assertion (use sparingly)
function getUserNameOrThrow(user: User): string {
if (!user.profile?.name) {
throw new Error('User has no name');
}
return user.profile.name;
}json
// tsconfig.json - Phase 2
{
"compilerOptions": {
"noImplicitAny": true,
"strictNullChecks": true
}
}typescript
// 之前:潜在空值错误
function getUserName(user: User) {
return user.profile.name; // 若profile为undefined则报错
}
// 之后:正确的空值处理
function getUserName(user: User): string | undefined {
return user.profile?.name;
}
// 使用非空断言(谨慎使用)
function getUserNameOrThrow(user: User): string {
if (!user.profile?.name) {
throw new Error('用户未设置名称');
}
return user.profile.name;
}Phase 3: Function Types
阶段3:函数类型
json
// tsconfig.json - Phase 3
{
"compilerOptions": {
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true
}
}typescript
// Before: contravariance issues
type Handler = (event: Event) => void;
const mouseHandler: Handler = (event: MouseEvent) => {
console.log(event.clientX); // Error with strictFunctionTypes
};
// After: proper variance
type Handler<T extends Event = Event> = (event: T) => void;
const mouseHandler: Handler<MouseEvent> = (event) => {
console.log(event.clientX);
};json
// tsconfig.json - Phase 3
{
"compilerOptions": {
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true
}
}typescript
// 之前:逆变问题
type Handler = (event: Event) => void;
const mouseHandler: Handler = (event: MouseEvent) => {
console.log(event.clientX); // 启用strictFunctionTypes后会报错
};
// 之后:正确的协变处理
type Handler<T extends Event = Event> = (event: T) => void;
const mouseHandler: Handler<MouseEvent> = (event) => {
console.log(event.clientX);
};Phase 4: Property Initialization
阶段4:属性初始化
json
// tsconfig.json - Phase 4
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true
}
}typescript
// Before: uninitialized properties
class UserService {
private apiClient: ApiClient; // Error: not initialized
constructor() {}
}
// After: definite assignment
class UserService {
private apiClient: ApiClient;
constructor(apiClient: ApiClient) {
this.apiClient = apiClient;
}
}
// Or with definite assignment assertion
class UserService {
private apiClient!: ApiClient; // Initialized in init()
async init() {
this.apiClient = await createApiClient();
}
}json
// tsconfig.json - Phase 4
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true
}
}typescript
// 之前:未初始化的属性
class UserService {
private apiClient: ApiClient; // 错误:未初始化
constructor() {}
}
// 之后:明确赋值
class UserService {
private apiClient: ApiClient;
constructor(apiClient: ApiClient) {
this.apiClient = apiClient;
}
}
// 或使用明确赋值断言
class UserService {
private apiClient!: ApiClient; // 在init()中初始化
async init() {
this.apiClient = await createApiClient();
}
}Type Guards
类型守卫
Basic Type Guards
基础类型守卫
typescript
// Type guard functions
function isString(value: unknown): value is string {
return typeof value === 'string';
}
function isNumber(value: unknown): value is number {
return typeof value === 'number';
}
function isObject(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null;
}
function isArray<T>(value: unknown, itemGuard: (item: unknown) => item is T): value is T[] {
return Array.isArray(value) && value.every(itemGuard);
}
// Usage
function processInput(input: unknown) {
if (isString(input)) {
return input.toUpperCase(); // input is string
}
if (isNumber(input)) {
return input.toFixed(2); // input is number
}
throw new Error('Invalid input type');
}typescript
// 类型守卫函数
function isString(value: unknown): value is string {
return typeof value === 'string';
}
function isNumber(value: unknown): value is number {
return typeof value === 'number';
}
function isObject(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null;
}
function isArray<T>(value: unknown, itemGuard: (item: unknown) => item is T): value is T[] {
return Array.isArray(value) && value.every(itemGuard);
}
// 使用示例
function processInput(input: unknown) {
if (isString(input)) {
return input.toUpperCase(); // input被推断为string类型
}
if (isNumber(input)) {
return input.toFixed(2); // input被推断为number类型
}
throw new Error('输入类型无效');
}Object Type Guards
对象类型守卫
typescript
interface User {
id: string;
name: string;
email: string;
role: 'admin' | 'user';
}
interface ApiResponse<T> {
data: T;
success: boolean;
}
// Type guard for User
function isUser(value: unknown): value is User {
return (
isObject(value) &&
typeof value.id === 'string' &&
typeof value.name === 'string' &&
typeof value.email === 'string' &&
(value.role === 'admin' || value.role === 'user')
);
}
// Type guard for API response
function isApiResponse<T>(
value: unknown,
dataGuard: (data: unknown) => data is T
): value is ApiResponse<T> {
return (
isObject(value) &&
typeof value.success === 'boolean' &&
'data' in value &&
dataGuard(value.data)
);
}
// Usage
async function fetchUser(id: string): Promise<User> {
const response = await fetch(`/api/users/${id}`);
const data: unknown = await response.json();
if (!isApiResponse(data, isUser)) {
throw new Error('Invalid API response');
}
return data.data;
}typescript
interface User {
id: string;
name: string;
email: string;
role: 'admin' | 'user';
}
interface ApiResponse<T> {
data: T;
success: boolean;
}
// User类型的守卫函数
function isUser(value: unknown): value is User {
return (
isObject(value) &&
typeof value.id === 'string' &&
typeof value.name === 'string' &&
typeof value.email === 'string' &&
(value.role === 'admin' || value.role === 'user')
);
}
// API响应的守卫函数
function isApiResponse<T>(
value: unknown,
dataGuard: (data: unknown) => data is T
): value is ApiResponse<T> {
return (
isObject(value) &&
typeof value.success === 'boolean' &&
'data' in value &&
dataGuard(value.data)
);
}
// 使用示例
async function fetchUser(id: string): Promise<User> {
const response = await fetch(`/api/users/${id}`);
const data: unknown = await response.json();
if (!isApiResponse(data, isUser)) {
throw new Error('API响应格式无效');
}
return data.data;
}Discriminated Unions
可辨识联合
typescript
// Discriminated union pattern
type Result<T, E = Error> =
| { success: true; data: T }
| { success: false; error: E };
function createSuccess<T>(data: T): Result<T> {
return { success: true, data };
}
function createError<E = Error>(error: E): Result<never, E> {
return { success: false, error };
}
// Type guard via discriminant
function isSuccess<T, E>(result: Result<T, E>): result is { success: true; data: T } {
return result.success === true;
}
// Usage
async function processRequest(): Promise<Result<User>> {
try {
const user = await fetchUser('123');
return createSuccess(user);
} catch (error) {
return createError(error instanceof Error ? error : new Error(String(error)));
}
}
const result = await processRequest();
if (isSuccess(result)) {
console.log(result.data.name); // TypeScript knows data exists
} else {
console.error(result.error.message); // TypeScript knows error exists
}typescript
// 可辨识联合模式
type Result<T, E = Error> =
| { success: true; data: T }
| { success: false; error: E };
function createSuccess<T>(data: T): Result<T> {
return { success: true, data };
}
function createError<E = Error>(error: E): Result<never, E> {
return { success: false, error };
}
// 通过辨识符实现类型守卫
function isSuccess<T, E>(result: Result<T, E>): result is { success: true; data: T } {
return result.success === true;
}
// 使用示例
async function processRequest(): Promise<Result<User>> {
try {
const user = await fetchUser('123');
return createSuccess(user);
} catch (error) {
return createError(error instanceof Error ? error : new Error(String(error)));
}
}
const result = await processRequest();
if (isSuccess(result)) {
console.log(result.data.name); // TypeScript已知data存在
} else {
console.error(result.error.message); // TypeScript已知error存在
}Utility Types for Migration
迁移用工具类型
typescript
// Making properties required
type RequiredUser = Required<User>;
// Making properties optional
type PartialUser = Partial<User>;
// Pick specific properties
type UserCredentials = Pick<User, 'email' | 'id'>;
// Omit specific properties
type PublicUser = Omit<User, 'password' | 'internalId'>;
// Make properties readonly
type ReadonlyUser = Readonly<User>;
// Deep readonly
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object
? DeepReadonly<T[P]>
: T[P];
};
// NonNullable
type DefiniteString = NonNullable<string | null | undefined>; // string
// Extract and Exclude
type AdminRole = Extract<User['role'], 'admin'>; // 'admin'
type NonAdminRole = Exclude<User['role'], 'admin'>; // 'user'
// Record type
type UserById = Record<string, User>;
// Parameters and ReturnType
type FetchParams = Parameters<typeof fetch>; // [input: RequestInfo, init?: RequestInit]
type FetchReturn = ReturnType<typeof fetch>; // Promise<Response>typescript
// 将属性设为必填
type RequiredUser = Required<User>;
// 将属性设为可选
type PartialUser = Partial<User>;
// 选取特定属性
type UserCredentials = Pick<User, 'email' | 'id'>;
// 排除特定属性
type PublicUser = Omit<User, 'password' | 'internalId'>;
// 将属性设为只读
type ReadonlyUser = Readonly<User>;
// 深度只读
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object
? DeepReadonly<T[P]>
: T[P];
};
// 非空类型
type DefiniteString = NonNullable<string | null | undefined>; // string
// 提取与排除
type AdminRole = Extract<User['role'], 'admin'>; // 'admin'
type NonAdminRole = Exclude<User['role'], 'admin'>; // 'user'
// 记录类型
type UserById = Record<string, User>;
// 参数与返回值类型
type FetchParams = Parameters<typeof fetch>; // [input: RequestInfo, init?: RequestInit]
type FetchReturn = ReturnType<typeof fetch>; // Promise<Response>Common Migration Patterns
常见迁移模式
Handling Optional Chaining
可选链处理
typescript
// Before: unsafe access
const userName = user.profile.settings.displayName;
// After: safe access with optional chaining
const userName = user?.profile?.settings?.displayName;
// With nullish coalescing
const userName = user?.profile?.settings?.displayName ?? 'Anonymous';
// With type narrowing
function getDisplayName(user: User | null): string {
if (!user?.profile?.settings?.displayName) {
return 'Anonymous';
}
return user.profile.settings.displayName;
}typescript
// 之前:不安全的属性访问
const userName = user.profile.settings.displayName;
// 之后:使用可选链安全访问
const userName = user?.profile?.settings?.displayName;
// 结合空值合并运算符
const userName = user?.profile?.settings?.displayName ?? '匿名用户';
// 结合类型收窄
function getDisplayName(user: User | null): string {
if (!user?.profile?.settings?.displayName) {
return '匿名用户';
}
return user.profile.settings.displayName;
}Assertion Functions
断言函数
typescript
// Assertion function
function assertIsDefined<T>(value: T): asserts value is NonNullable<T> {
if (value === undefined || value === null) {
throw new Error('Value is not defined');
}
}
function assertIsUser(value: unknown): asserts value is User {
if (!isUser(value)) {
throw new Error('Value is not a User');
}
}
// Usage
function processUser(maybeUser: unknown) {
assertIsUser(maybeUser);
// maybeUser is now User
console.log(maybeUser.name);
}typescript
// 断言函数
function assertIsDefined<T>(value: T): asserts value is NonNullable<T> {
if (value === undefined || value === null) {
throw new Error('值未定义');
}
}
function assertIsUser(value: unknown): asserts value is User {
if (!isUser(value)) {
throw new Error('值不是User类型');
}
}
// 使用示例
function processUser(maybeUser: unknown) {
assertIsUser(maybeUser);
// maybeUser现在被推断为User类型
console.log(maybeUser.name);
}Error Handling
错误处理
typescript
// Before: any in catch
try {
await riskyOperation();
} catch (error) {
console.error(error.message); // Error with useUnknownInCatchVariables
}
// After: proper error handling
try {
await riskyOperation();
} catch (error) {
if (error instanceof Error) {
console.error(error.message);
} else {
console.error('Unknown error:', String(error));
}
}
// Helper function
function getErrorMessage(error: unknown): string {
if (error instanceof Error) return error.message;
if (typeof error === 'string') return error;
return 'Unknown error occurred';
}typescript
// 之前:catch中使用any类型
try {
await riskyOperation();
} catch (error) {
console.error(error.message); // 启用useUnknownInCatchVariables后会报错
}
// 之后:正确的错误处理
try {
await riskyOperation();
} catch (error) {
if (error instanceof Error) {
console.error(error.message);
} else {
console.error('未知错误:', String(error));
}
}
// 辅助函数
function getErrorMessage(error: unknown): string {
if (error instanceof Error) return error.message;
if (typeof error === 'string') return error;
return '发生未知错误';
}Index Signatures
索引签名
typescript
// Before: unsafe index access
const users: Record<string, User> = {};
const user = users['unknown-id'];
console.log(user.name); // Error with noUncheckedIndexedAccess
// After: proper null check
const user = users['unknown-id'];
if (user) {
console.log(user.name);
}
// Or with assertion
const user = users['known-id']!; // Only if you're certain
// Better: use Map
const usersMap = new Map<string, User>();
const user = usersMap.get('some-id'); // User | undefined by designtypescript
// 之前:不安全的索引访问
const users: Record<string, User> = {};
const user = users['unknown-id'];
console.log(user.name); // 启用noUncheckedIndexedAccess后会报错
// 之后:正确的空值检查
const user = users['unknown-id'];
if (user) {
console.log(user.name);
}
// 或使用断言(仅当确定存在时使用)
const user = users['known-id']!;
// 更佳方案:使用Map
const usersMap = new Map<string, User>();
const user = usersMap.get('some-id'); // 天然为User | undefined类型Migration Script
迁移脚本
typescript
// scripts/analyze-strict.ts
import * as ts from 'typescript';
import * as path from 'path';
interface StrictAnalysis {
noImplicitAny: number;
strictNullChecks: number;
strictFunctionTypes: number;
strictPropertyInitialization: number;
total: number;
}
function analyzeProject(configPath: string): StrictAnalysis {
const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
const parsedConfig = ts.parseJsonConfigFileContent(
configFile.config,
ts.sys,
path.dirname(configPath)
);
// Enable strict flags one by one
const strictOptions = {
noImplicitAny: true,
strictNullChecks: true,
strictFunctionTypes: true,
strictPropertyInitialization: true,
};
const analysis: StrictAnalysis = {
noImplicitAny: 0,
strictNullChecks: 0,
strictFunctionTypes: 0,
strictPropertyInitialization: 0,
total: 0,
};
for (const [flag, _] of Object.entries(strictOptions)) {
const options = {
...parsedConfig.options,
[flag]: true,
};
const program = ts.createProgram(parsedConfig.fileNames, options);
const diagnostics = ts.getPreEmitDiagnostics(program);
analysis[flag as keyof StrictAnalysis] = diagnostics.length;
analysis.total += diagnostics.length;
}
return analysis;
}
// Usage
const analysis = analyzeProject('./tsconfig.json');
console.log('Strict mode analysis:', analysis);typescript
// scripts/analyze-strict.ts
import * as ts from 'typescript';
import * as path from 'path';
interface StrictAnalysis {
noImplicitAny: number;
strictNullChecks: number;
strictFunctionTypes: number;
strictPropertyInitialization: number;
total: number;
}
function analyzeProject(configPath: string): StrictAnalysis {
const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
const parsedConfig = ts.parseJsonConfigFileContent(
configFile.config,
ts.sys,
path.dirname(configPath)
);
// 逐个启用严格模式标志
const strictOptions = {
noImplicitAny: true,
strictNullChecks: true,
strictFunctionTypes: true,
strictPropertyInitialization: true,
};
const analysis: StrictAnalysis = {
noImplicitAny: 0,
strictNullChecks: 0,
strictFunctionTypes: 0,
strictPropertyInitialization: 0,
total: 0,
};
for (const [flag, _] of Object.entries(strictOptions)) {
const options = {
...parsedConfig.options,
[flag]: true,
};
const program = ts.createProgram(parsedConfig.fileNames, options);
const diagnostics = ts.getPreEmitDiagnostics(program);
analysis[flag as keyof StrictAnalysis] = diagnostics.length;
analysis.total += diagnostics.length;
}
return analysis;
}
// 使用示例
const analysis = analyzeProject('./tsconfig.json');
console.log('严格模式分析结果:', analysis);Best Practices
最佳实践
- Incremental adoption: One flag at a time
- Start with noImplicitAny: Easiest to fix
- Add type guards: Runtime safety
- Use assertion functions: Fail fast
- Avoid non-null assertions: Use sparingly
- Document patterns: Team consistency
- CI enforcement: Prevent regression
- Use unknown over any: Better type safety
- 渐进式采用:每次启用一个标志
- 从noImplicitAny开始:最容易修复
- 添加类型守卫:保障运行时安全
- 使用断言函数:快速失败
- 避免非空断言:谨慎使用
- 记录模式:保持团队一致性
- CI强制执行:防止回归
- 优先使用unknown而非any:更好的类型安全
Output Checklist
输出检查清单
Every strict migration should include:
- Baseline error count per flag
- Migration plan with phases
- Type guard utilities
- Assertion functions
- Error handling patterns
- Index access handling
- Optional chaining usage
- Updated tsconfig.json
- Team documentation
- CI strict checking
每次严格模式迁移都应包含:
- 各标志的基准错误数
- 分阶段迁移计划
- 类型守卫工具
- 断言函数
- 错误处理模式
- 索引访问处理方案
- 可选链使用示例
- 更新后的tsconfig.json
- 团队文档
- CI严格检查配置