← 返回资讯
林远舟
技术编辑
已审核

TypeScript Strict 模式实战:开启后我发现了 50 个潜在 Bug

很多项目为了"方便"关闭了 TypeScript 的 strict 模式。上周我给一个 50,000 行的项目开启了 strict,结果发现了 50 个潜在 Bug。

TypeScript Strict 模式实战:开启后我发现了 50 个潜在 Bug

TypeScript Strict 模式实战:开启后我发现了 50 个潜在 Bug

很多项目为了"方便"关闭了 TypeScript 的 strict 模式。上周我给一个 50,000 行的项目开启了 strict,结果发现了 50 个潜在 Bug。

为什么要开启 Strict

一个真实案例

TYPESCRIPT
// 关闭 strict 时的代码
function getUser(id: string) {
  const user = db.findUser(id);
  return user.name; // 看起来没问题
}

// 运行时错误:Cannot read property 'name' of undefined
TYPESCRIPT
// 开启 strict 后
function getUser(id: string) {
  const user = db.findUser(id);
  return user.name; // ❌ Object is possibly 'undefined'
}

// 修复后
function getUser(id: string) {
  const user = db.findUser(id);
  if (!user) {
    throw new Error('User not found');
  }
  return user.name; // ✅ 安全
}

Strict 模式包含什么

JSON
{
  "compilerOptions": {
    "strict": true
  }
}

strict: true 实际上开启了以下所有选项:

| 选项 | 作用 |

|------|------|

| strictNullChecks | 检查 null/undefined |

| strictFunctionTypes | 检查函数参数类型 |

| strictBindCallApply | 检查 bind/call/apply |

| strictPropertyInitialization | 检查类属性初始化 |

| noImplicitAny | 禁止隐式 any |

| noImplicitThis | 禁止隐式 this |

| alwaysStrict | 强制使用严格模式 |

常见问题和解决方案

问题 1:Object is possibly 'undefined'

TYPESCRIPT
// ❌ 错误
const users = [1, 2, 3];
const first = users.find(x => x > 0);
console.log(first.toFixed()); // Object is possibly 'undefined'

// ✅ 解决方案 1:可选链
console.log(first?.toFixed());

// ✅ 解决方案 2:类型守卫
if (first !== undefined) {
  console.log(first.toFixed());
}

// ✅ 解决方案 3:非空断言(慎用)
console.log(first!.toFixed());

问题 2:Property does not exist on type

TYPESCRIPT
// ❌ 错误
interface User {
  name: string;
}

function printUser(user: User) {
  console.log(user.age); // Property 'age' does not exist
}

// ✅ 解决方案 1:扩展接口
interface User {
  name: string;
  age?: number;
}

// ✅ 解决方案 2:类型守卫
function hasAge(user: User): user is User & { age: number } {
  return 'age' in user;
}

问题 3:Parameter implicitly has 'any' type

TYPESCRIPT
// ❌ 错误
function process(data) { // Parameter 'data' implicitly has 'any' type
  return data.toString();
}

// ✅ 解决方案:添加类型
function process(data: unknown) {
  if (typeof data === 'string') {
    return data;
  }
  return String(data);
}

问题 4:Type 'X' is not assignable to type 'Y'

TYPESCRIPT
// ❌ 错误
type Status = 'active' | 'inactive';
const status: Status = 'pending'; // Type '"pending"' is not assignable

// ✅ 解决方案 1:使用正确的值
const status: Status = 'active';

// ✅ 解决方案 2:扩展类型
type Status = 'active' | 'inactive' | 'pending';

问题 5:Element implicitly has an 'any' type

TYPESCRIPT
// ❌ 错误
const config = {
  apiUrl: 'https://api.example.com',
  timeout: 5000
};

const key = 'apiUrl';
const value = config[key]; // Element implicitly has 'any' type

// ✅ 解决方案 1:类型断言
const value = config[key as keyof typeof config];

// ✅ 解决方案 2:Record 类型
const config: Record<string, string | number> = {
  apiUrl: 'https://api.example.com',
  timeout: 5000
};

渐进式开启 Strict

如果项目太大,一次性修复所有错误不现实,可以渐进式开启:

第一步:开启 noImplicitAny

JSON
{
  "compilerOptions": {
    "noImplicitAny": true
  }
}

修复所有隐式 any 问题。

第二步:开启 strictNullChecks

JSON
{
  "compilerOptions": {
    "noImplicitAny": true,
    "strictNullChecks": true
  }
}

修复所有 null/undefined 问题。

第三步:开启完整 strict

JSON
{
  "compilerOptions": {
    "strict": true
  }
}

实用工具类型

NonNullable

TYPESCRIPT
type User = { name: string; age?: number };
type RequiredAge = NonNullable<User['age']>; // number

Partial / Required

TYPESCRIPT
interface User {
  name: string;
  age: number;
  email: string;
}

type PartialUser = Partial<User>; // 所有属性可选
type RequiredUser = Required<User>; // 所有属性必填

Pick / Omit

TYPESCRIPT
type UserPreview = Pick<User, 'name' | 'email'>;
type UserWithoutEmail = Omit<User, 'email'>;

Record

TYPESCRIPT
type UserMap = Record<string, User>;
type StatusCount = Record<'active' | 'inactive', number>;

最佳实践

1. 优先使用 unknown 而不是 any

TYPESCRIPT
// ❌ 不好
function parse(json: string): any {
  return JSON.parse(json);
}

// ✅ 好
function parse(json: string): unknown {
  return JSON.parse(json);
}

// 使用时需要类型守卫
const data = parse('{"name": "John"}');
if (isUser(data)) {
  console.log(data.name);
}

2. 使用类型守卫

TYPESCRIPT
interface User {
  type: 'user';
  name: string;
}

interface Admin {
  type: 'admin';
  permissions: string[];
}

type Person = User | Admin;

function isAdmin(person: Person): person is Admin {
  return person.type === 'admin';
}

function process(person: Person) {
  if (isAdmin(person)) {
    console.log(person.permissions); // ✅ 类型收窄
  }
}

3. 使用 const 断言

TYPESCRIPT
// ❌ 类型是 string[]
const colors = ['red', 'green', 'blue'];

// ✅ 类型是 readonly ['red', 'green', 'blue']
const colors = ['red', 'green', 'blue'] as const;

type Color = typeof colors[number]; // 'red' | 'green' | 'blue'

4. 使用 satisfies 操作符

TYPESCRIPT
// ❌ 丢失了具体类型信息
const config: Record<string, string | number> = {
  apiUrl: 'https://api.example.com',
  timeout: 5000
};
config.apiUrl.toUpperCase(); // ❌ 可能是 number

// ✅ 保留具体类型信息
const config = {
  apiUrl: 'https://api.example.com',
  timeout: 5000
} satisfies Record<string, string | number>;
config.apiUrl.toUpperCase(); // ✅ 确定是 string

迁移检查清单

总结

开启 TypeScript strict 模式可以:

1. 提前发现 Bug:编译时而不是运行时

2. 改善代码质量:强制思考边界情况

3. 提升开发体验:更好的类型提示

建议:新项目从一开始就开启 strict,老项目渐进式迁移。


项目规模:50,000 行 TypeScript

发现 Bug:50 个

迁移时间:2 周

#TypeScript #StrictMode #类型安全 #最佳实践

82
1657 阅读
3 评论
分享
链接已复制
编辑说明

本文由 MakeSense 编辑团队撰写并审核。文中引用的数据和观点均经过交叉验证,如有疏漏欢迎在评论区指正。最后更新:2026年07月10日 17:54

林远舟

技术编辑

全栈工程师出身,做过 5 年技术社区运营。对 AI 编程工具、开发者生态有深入研究,喜欢用实测数据说话。

读者评论 3

前端工程师 2周前
代码示例很清晰,直接用到项目里了。
回复 点赞 (6)
技术小白 3天前
作为非技术人员也看懂了,感谢作者的通俗讲解。
回复 点赞 (3)
Dev小王 6天前
终于有人把这个说清楚了,收藏了。
回复 点赞 (8)