跳至主要内容
Rosecraft Studios

TypeScript 严格模式:为什么我们坚持开启它

通过具体示例了解严格编译检查如何在代码进入生产环境之前发现错误,以及它仍不能替代哪些运行时检查。

Corey Rosamond · 2026/2/15 · 约 5 分钟阅读

本文为英文原文的简体中文译文。资料和事件日期保留原文发表时的语境;代码示例保持原样。 Read in English

TypeScript 严格模式:为什么我们坚持开启它

为什么使用严格模式

Rosecraft Studios 的 TypeScript 项目以 strict: true 为起点。多年软件工作让我们体会到,在编译阶段发现错误,通常比凌晨两点在生产环境发现更容易处理。

严格模式启用一组编译器检查,可以在执行前发现部分错误。但静态类型不会验证网络响应,也无法消除所有运行时错误。

严格模式究竟启用了什么

{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true
  }
}

strict 启用一组检查,包括下面这些示例。上方的 noUncheckedIndexedAccess 和未使用代码检查是独立选项,并不由 strict 自动开启。完整列表请查看对应编译器版本的 strict 参考文档:

  • strictNullChecks:区分空值,要求类型和处理方式与可能为空的情况一致。
  • strictFunctionTypes:更严格地检查函数参数类型兼容性。
  • strictBindCallApply:检查 bind、call 和 apply 的类型。
  • strictPropertyInitialization:检查类属性初始化。
  • noImplicitAny:报告无法推断而隐式成为 any 的情况。
  • noImplicitThis:检查隐式 any 类型的 this。
  • alwaysStrict:按严格模式解析并输出相应的 "use strict"。

严格检查能发现的错误

1. 未处理的空引用

没有 strictNullChecks 时,下面的代码可以编译,却可能在运行时崩溃:

// Without strict: compiles, crashes at runtime
function getUserName(users: User[], id: string) {
  const user = users.find((u) => u.id === id);
  return user.name; // Runtime error: Cannot read property 'name' of undefined
}

// With strict: compile error forces proper handling
function getUserName(users: User[], id: string) {
  const user = users.find((u) => u.id === id);
  if (!user) {
    throw new Error(`User not found: ${id}`);
  }
  return user.name; // Safe — TypeScript knows user is not undefined
}

Array.find() 返回 T | undefined。关闭严格空值检查后,编译器不会要求处理找不到记录的情况。显式检查让您决定应用此时应该如何响应。

2. 索引访问陷阱

即便启用 strictNullChecks,默认数组和对象索引访问仍可能掩盖不存在的值。因此我们额外启用 noUncheckedIndexedAccess:

const colors = ['red', 'green', 'blue'];

// Without noUncheckedIndexedAccess
const first = colors[0]; // Type: string (wrong — could be undefined)

// With noUncheckedIndexedAccess
const first = colors[0]; // Type: string | undefined (correct)

// Forces safe access patterns
if (first) {
  console.error(first.toUpperCase()); // Safe
}

3. 隐式 any 隐藏的问题

// Without noImplicitAny: compiles, returns wrong type
function parseConfig(raw) {
  // 'raw' is implicitly 'any'
  return raw.settings.theme; // No type checking at all
}

// With noImplicitAny: forces explicit typing
function parseConfig(raw: RawConfig): string {
  return raw.settings.theme; // Fully type-checked
}

在 unknown 与 any 之间作选择

类型确实不确定时,优先使用 unknown,而不是 any。差别在于使用前是否必须检查:

// 'any' disables ALL type checking
function processData(data: any) {
  data.forEach((item) => item.transform()); // No errors, no safety
}

// 'unknown' requires narrowing before use
function processData(data: unknown) {
  if (!Array.isArray(data)) {
    throw new Error('Expected an array');
  }
  // TypeScript now knows data is an array
  data.forEach((item: unknown) => {
    if (isTransformable(item)) {
      item.transform(); // Safe — type guard verified the shape
    }
  });
}

any 像是在告诉编译器“不用检查我”,而 unknown 则是“我现在不知道,但使用前会检查”。后者更明确。

Zod:连接运行时验证与静态类型

在 API 路由、表单处理器和外部 API 响应等系统边界,我们使用 Zod schema 同时定义验证规则与类型:

import { z } from 'zod';

const contactSchema = z.object({
  name: z.string().min(2).max(100),
  email: z.string().email(),
  message: z.string().min(10).max(2000),
});

// Infer the TypeScript type from the Zod schema
type ContactForm = z.infer<typeof contactSchema>;

// API route uses the same schema for validation
export async function POST(request: Request) {
  const body = await request.json();
  const result = contactSchema.safeParse(body);

  if (!result.success) {
    return Response.json(
      { error: 'Validation failed', details: result.error.flatten() },
      { status: 400 },
    );
  }

  // result.data is fully typed as ContactForm
  await saveSubmission(result.data);
}

这种方式可以减少 TypeScript 接口与运行时验证各自维护、逐渐不一致的问题。

迁移到严格模式

现有代码库尚未开启严格模式时,一次性迁移可能很困难。可以逐步推进:

  1. 一次启用一个检查。例如先处理 strictNullChecks,再处理 noImplicitAny。
  2. 逐个文件修复。临时使用 // @ts-expect-error 时,配上可追踪的说明。
  3. 不再引入新的 any。新代码使用合适类型。
  4. 随后加入 noUncheckedIndexedAccess。它可能涉及较多索引访问修改。

从能处理代码库已知风险的检查开始。分别记录编译错误与实际缺陷,不要把错误数量当成“防止了多少百分比生产问题”的测量结果。

将静态检查与运行时检查结合

严格模式让开发中的假设更容易看见。还应结合输入验证、现实测试和运行时错误处理。类型断言、无类型依赖和外部数据,仍可能使编译器看来安全的假设失效。

希望用扎实的工程实践开发 TypeScript 项目?聊聊您的需求,一起把这些实践落实为可靠软件。

作者:Corey Rosamond,Rosecraft 创始人兼首席工程师

继续阅读

订阅工作室邮件

留下邮箱以接收工作室更新。订阅后请检查确认邮件;确认邮件目前使用英文。

了解隐私政策

下一步,从交流开始

把您的问题,变成清晰的下一步。

告诉我们您想开发或改进什么。我们会一起梳理范围、限制和合适的工程方案。

聊聊项目