Skip to content

严格模式配置

概述

TypeScript 的严格模式(Strict Mode)是一组类型检查选项的集合,旨在提高代码的类型安全性和质量。启用严格模式后,TypeScript 会进行更严格的类型检查,帮助开发者在编译时发现潜在的错误,而不是等到运行时才发现问题。

严格模式的核心价值:

  • 提前发现错误:在编译阶段捕获类型相关的错误
  • 提高代码质量:强制开发者编写更明确的类型注解
  • 更好的 IDE 支持:更准确的类型推断和自动补全
  • 减少运行时错误:通过严格的类型检查避免常见的运行时问题

启用严格模式

快速启用

最简单的方式是启用 strict 选项,它会自动启用所有严格类型检查选项:

json
{
  "compilerOptions": {
    "strict": true
  }
}

单独控制

如果你需要更细粒度的控制,可以关闭 strict,然后单独启用或关闭各个子选项:

json
{
  "compilerOptions": {
    "strict": false,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictBindCallApply": true,
    "strictPropertyInitialization": true,
    "noImplicitThis": true,
    "alwaysStrict": true
  }
}

严格模式选项详解

noImplicitAny

不允许隐式的 any 类型。当 TypeScript 无法推断出类型时,默认会使用 any,启用此选项后,必须显式指定类型。

typescript
// ❌ 错误:参数 'x' 隐式具有 'any' 类型
function process(x) {
  return x * 2;
}

// ✅ 正确:明确指定类型
function process(x: number): number {
  return x * 2;
}

// ✅ 正确:使用类型推断
function process(x: number) {
  return x * 2; // 返回类型自动推断为 number
}

实际应用场景

typescript
// 处理 API 响应
interface ApiResponse {
  data: unknown;
  status: number;
}

// ❌ 错误:隐式 any
function handleResponse(response) {
  return response.data.items;
}

// ✅ 正确:明确类型
function handleResponse(response: ApiResponse) {
  if (response.status === 200) {
    return response.data;
  }
  throw new Error('Request failed');
}

提示

noImplicitAny 是严格模式中最基础的选项,建议所有项目都启用。它强制开发者明确类型,避免类型不明确导致的错误。

strictNullChecks

严格检查 nullundefined。启用后,nullundefined 不能赋值给其他类型,除非明确声明为联合类型。

typescript
// strictNullChecks: false(默认行为)
let name: string;
name = null; // ✅ 允许
name = undefined; // ✅ 允许

// strictNullChecks: true
let name: string;
name = null; // ❌ 错误:不能将类型 "null" 分配给类型 "string"
name = undefined; // ❌ 错误:不能将类型 "undefined" 分配给类型 "string"

// ✅ 正确:使用联合类型
let name: string | null | undefined;
name = null; // ✅ 允许
name = undefined; // ✅ 允许
name = "TypeScript"; // ✅ 允许

实际应用场景

typescript
// 处理可能为 null 的返回值
function findUser(id: number): User | null {
  const user = users.find(u => u.id === id);
  return user || null;
}

// 必须检查 null
const user = findUser(1);
if (user !== null) {
  console.log(user.name); // ✅ 类型安全,TypeScript 知道 user 不是 null
} else {
  console.log('User not found');
}

// 使用可选链和空值合并
const userName = findUser(1)?.name ?? 'Unknown';

DOM 操作示例

typescript
// ❌ 错误:getElementById 可能返回 null
const element = document.getElementById('myInput');
element.value = 'hello'; // 错误:'element' 可能为 'null'

// ✅ 正确:检查 null
const element = document.getElementById('myInput');
if (element !== null) {
  element.value = 'hello';
}

// ✅ 正确:使用类型断言(如果确定元素存在)
const element = document.getElementById('myInput') as HTMLInputElement;
element.value = 'hello';

// ✅ 正确:使用非空断言(谨慎使用)
const element = document.getElementById('myInput')!;
element.value = 'hello';

注意

strictNullChecks 是严格模式中最重要的选项之一,启用后需要大量修改代码。建议在项目初期就启用,或者逐步迁移。

strictFunctionTypes

严格检查函数类型。启用后,函数类型检查遵循逆变(contravariant)规则,确保函数参数类型的安全性。

typescript
// 基础类型
type Handler = (event: Event) => void;

// 子类型
type MouseHandler = (event: MouseEvent) => void;

// strictFunctionTypes: false(默认)
let handler: Handler;
handler = (event: MouseEvent) => {
  console.log(event.clientX); // 可能不安全
}; // ✅ 允许(不严格)

// strictFunctionTypes: true
let handler: Handler;
handler = (event: MouseEvent) => {}; // ❌ 错误:类型不兼容

// ✅ 正确:使用更宽泛的类型
let mouseHandler: MouseHandler;
mouseHandler = (event: Event) => {
  // 只能访问 Event 的属性
  console.log(event.type);
};

实际应用场景

typescript
// 事件处理系统
interface EventEmitter {
  on(event: string, handler: (data: unknown) => void): void;
}

class MyEmitter implements EventEmitter {
  on(event: string, handler: (data: unknown) => void): void {
    // 实现
  }
}

// strictFunctionTypes: true 确保类型安全
const emitter = new MyEmitter();

// ✅ 正确:handler 参数类型是 unknown 的子类型
emitter.on('click', (data: string) => {
  console.log(data.toUpperCase());
});

// ❌ 错误:handler 参数类型不能是 unknown 的父类型
emitter.on('click', (data: any) => {
  // 类型不安全
});

strictBindCallApply

严格检查 bindcallapply 方法的参数类型。

typescript
function greet(name: string, age: number): string {
  return `Hello, ${name}! You are ${age} years old.`;
}

// strictBindCallApply: false
const bound = greet.bind(null, "Alice"); // 缺少参数,但不会报错
bound(30); // 运行时可能出错

// strictBindCallApply: true
const bound = greet.bind(null, "Alice"); // ❌ 错误:缺少参数 'age'
const bound = greet.bind(null, "Alice", 30); // ✅ 正确
bound(); // ✅ 正确:所有参数已绑定

实际应用场景

typescript
// 事件处理
class Button {
  constructor(private label: string) {}

  onClick(handler: (this: Button, event: Event) => void) {
    // 绑定 this 上下文
    handler.call(this, new Event('click'));
  }
}

const button = new Button('Submit');

// strictBindCallApply: true 确保参数类型正确
button.onClick(function(event) {
  console.log(this.label); // ✅ this 类型正确
  console.log(event.type); // ✅ event 类型正确
});

strictPropertyInitialization

严格检查类属性是否在构造函数中初始化。

typescript
// strictPropertyInitialization: true
class User {
  name: string; // ❌ 错误:属性 'name' 没有初始化
  age: number; // ❌ 错误:属性 'age' 没有初始化
}

// ✅ 解决方案 1:在构造函数中初始化
class User {
  name: string;
  age: number;
  
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}

// ✅ 解决方案 2:使用明确赋值断言(告诉 TypeScript 我们会在其他地方初始化)
class User {
  name!: string; // 使用 ! 断言
  age!: number;
  
  init(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}

// ✅ 解决方案 3:使用默认值
class User {
  name: string = '';
  age: number = 0;
}

// ✅ 解决方案 4:使用可选属性(如果确实可选)
class User {
  name?: string;
  age?: number;
}

实际应用场景

typescript
// 依赖注入场景
class UserService {
  private db!: Database; // 使用 ! 断言,由依赖注入框架初始化
  private cache!: Cache;

  // 由框架调用
  setDependencies(db: Database, cache: Cache) {
    this.db = db;
    this.cache = cache;
  }

  async getUser(id: number): Promise<User | null> {
    // 使用前确保已初始化
    return this.db.findUser(id);
  }
}

// React 组件场景
class UserProfile extends React.Component {
  private userData!: UserData; // 在 componentDidMount 中初始化

  componentDidMount() {
    this.userData = fetchUserData();
  }

  render() {
    return <div>{this.userData.name}</div>;
  }
}

noImplicitThis

不允许隐式的 this 类型。启用后,函数中的 this 必须有明确的类型。

typescript
// noImplicitThis: false
function handleClick() {
  console.log(this.value); // this 类型不明确
}

// noImplicitThis: true
function handleClick() {
  console.log(this.value); // ❌ 错误:'this' 隐式具有 'any' 类型
}

// ✅ 解决方案 1:使用箭头函数(this 从外部作用域继承)
class Button {
  value = 'Click me';
  
  handleClick = () => {
    console.log(this.value); // ✅ 正确:this 指向 Button 实例
  };
}

// ✅ 解决方案 2:明确指定 this 类型
function handleClick(this: HTMLButtonElement) {
  console.log(this.value); // ✅ 正确:this 类型明确
}

// ✅ 解决方案 3:使用 bind
class Button {
  value = 'Click me';
  
  constructor() {
    this.handleClick = this.handleClick.bind(this);
  }
  
  handleClick() {
    console.log(this.value); // ✅ 正确:this 已绑定
  }
}

实际应用场景

typescript
// DOM 事件处理
interface Clickable {
  onClick(this: Clickable, event: MouseEvent): void;
}

class Button implements Clickable {
  label = 'Submit';
  
  onClick(this: Button, event: MouseEvent) {
    console.log(`${this.label} clicked`);
    console.log(`Position: ${event.clientX}, ${event.clientY}`);
  }
}

// 数组方法中的 this
interface ArrayProcessor {
  process(item: number): number;
}

const processor: ArrayProcessor = {
  multiplier: 2,
  process(item: number): number {
    // ❌ 错误:this 类型不明确
    return item * this.multiplier;
  }
};

// ✅ 正确:使用箭头函数
const processor: ArrayProcessor = {
  multiplier: 2,
  process: (item: number) => {
    // 箭头函数不绑定 this,需要从外部访问
    return item * processor.multiplier;
  }
};

alwaysStrict

以严格模式解析代码,并在每个生成的文件顶部输出 "use strict"

typescript
// alwaysStrict: true
// 生成的 JavaScript 文件顶部会有 "use strict"

// 严格模式的行为:
// 1. 不允许使用未声明的变量
function test() {
  undeclaredVar = 10; // ❌ 错误(严格模式)
}

// 2. 不允许删除变量、函数或参数
function test(x: number) {
  delete x; // ❌ 错误(严格模式)
}

// 3. 不允许重复的参数名
function test(x: number, x: string) { // ❌ 错误(严格模式)
  // ...
}

提示

alwaysStrict 主要影响生成的 JavaScript 代码,对 TypeScript 类型检查影响较小。现代 JavaScript 环境(ES6+ 模块)默认就是严格模式。

逐步启用严格模式

对于现有项目,建议逐步启用严格模式,而不是一次性全部启用。

步骤 1:启用基础选项

json
{
  "compilerOptions": {
    "noImplicitAny": true,
    "strictNullChecks": false  // 先不启用,影响较大
  }
}

步骤 2:启用 null 检查

json
{
  "compilerOptions": {
    "noImplicitAny": true,
    "strictNullChecks": true  // 逐步启用
  }
}

修复所有 strictNullChecks 相关的错误后,继续下一步。

步骤 3:启用完整严格模式

json
{
  "compilerOptions": {
    "strict": true  // 启用所有严格选项
  }
}

迁移策略示例

typescript
// 迁移前:strictNullChecks: false
function processUser(user: User) {
  return user.name.toUpperCase(); // 可能 user 为 null
}

// 迁移步骤 1:添加类型检查
function processUser(user: User | null) {
  if (user === null) {
    return 'Unknown';
  }
  return user.name.toUpperCase();
}

// 迁移步骤 2:使用可选链(更简洁)
function processUser(user: User | null) {
  return user?.name.toUpperCase() ?? 'Unknown';
}

实际配置示例

新项目推荐配置

json
{
  "compilerOptions": {
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true
  }
}

现有项目迁移配置

json
{
  "compilerOptions": {
    "strict": false,
    "noImplicitAny": true,
    "strictNullChecks": false,  // 逐步启用
    "strictFunctionTypes": true,
    "strictBindCallApply": true,
    "strictPropertyInitialization": false,  // 稍后启用
    "noImplicitThis": true,
    "alwaysStrict": true
  }
}

库项目配置

json
{
  "compilerOptions": {
    "strict": true,
    "declaration": true,
    "declarationMap": true,
    "skipLibCheck": true
  }
}

常见问题和解决方案

问题 1:第三方库类型不兼容

问题:启用严格模式后,第三方库的类型定义可能不兼容。

解决方案

typescript
// 方案 1:使用类型断言
import { SomeLibrary } from 'some-library';
const result = (SomeLibrary as any).method();

// 方案 2:创建类型声明文件
// types/some-library.d.ts
declare module 'some-library' {
  export function method(): string;
}

// 方案 3:使用 @ts-ignore(不推荐,临时方案)
// @ts-ignore
const result = SomeLibrary.method();

问题 2:DOM API 类型检查

问题document.getElementById 返回 HTMLElement | null,需要频繁检查。

解决方案

typescript
// 创建辅助函数
function getElementById<T extends HTMLElement>(
  id: string
): T {
  const element = document.getElementById(id);
  if (element === null) {
    throw new Error(`Element with id "${id}" not found`);
  }
  return element as T;
}

// 使用
const input = getElementById<HTMLInputElement>('myInput');
input.value = 'hello'; // ✅ 类型安全

问题 3:类属性初始化

问题:某些属性需要在构造函数外初始化。

解决方案

typescript
// 使用明确赋值断言
class ApiClient {
  private config!: ApiConfig; // 由框架初始化
  
  setConfig(config: ApiConfig) {
    this.config = config;
  }
}

// 或使用可选属性
class ApiClient {
  private config?: ApiConfig;
  
  getConfig(): ApiConfig {
    if (!this.config) {
      throw new Error('Config not initialized');
    }
    return this.config;
  }
}

注意事项

重要提示

  1. 迁移成本:启用严格模式,特别是 strictNullChecks,可能需要大量修改现有代码。建议在项目初期就启用,或制定逐步迁移计划。

  2. 第三方库兼容性:某些第三方库的类型定义可能不完全兼容严格模式,需要创建类型声明文件或使用类型断言。

  3. 性能影响:严格模式会增加类型检查的时间,但通常影响很小,带来的好处远大于成本。

  4. 团队协作:确保团队成员了解严格模式的要求,统一代码风格和类型注解规范。

最佳实践

  1. 新项目:从一开始就启用 strict: true,避免后续迁移成本。

  2. 现有项目:逐步启用,先启用影响较小的选项(如 noImplicitAny),再启用影响较大的选项(如 strictNullChecks)。

  3. 代码审查:在代码审查中关注类型安全性,确保正确使用类型注解。

  4. 工具支持:使用 ESLint 的 TypeScript 规则配合严格模式,进一步提高代码质量。

  5. 文档化:对于复杂的类型定义,添加注释说明,帮助团队成员理解。

相关链接

基于 VitePress 构建