
为什么严格模式很重要
没有严格模式的 TypeScript 只是带有可选类型注解的 JavaScript。而启用严格模式的 TypeScript 则是一种不同的语言,它能在编译时捕获一整类 bug。
两者之间的差距比大多数开发者意识到的要大。当 strictNullChecks: false 时,null 和 undefined 是所有类型的子类型——这意味着类型检查器会愉快地接受那些在运行时因“Cannot read properties of undefined”而崩溃的代码。这是最常见的 JavaScript 运行时错误,而且是可以预防的。

“strict”实际启用了什么
strict: true 是 tsconfig.json 中启用多个标志的简写:
{
"compilerOptions": {
// 以下全部由 "strict: true" 启用:
"strictNullChecks": true, // null/undefined 是独立的类型
"noImplicitAny": true, // 不能使用隐式 any
"strictFunctionTypes": true, // 函数参数类型逆变检查
"strictBindCallApply": true, // bind/call/apply 具有正确的类型
"strictPropertyInitialization": true, // 类属性必须初始化
"noImplicitThis": true, // 'this' 必须有显式类型
"alwaysStrict": true, // 在 JS 输出中生成 "use strict"
"useUnknownInCatchVariables": true // catch 变量为 'unknown' 而非 'any'
}
}
另外一些有价值的选项(不在 strict 捆绑中):
{
"compilerOptions": {
"noUncheckedIndexedAccess": true, // array[n] 可能为 undefined
"exactOptionalPropertyTypes": true, // {a?: string} ≠ {a: string | undefined}
"noImplicitReturns": true, // 所有代码路径都必须返回
"noFallthroughCasesInSwitch": true, // 禁止 switch 意外穿透
"noImplicitOverride": true, // 类覆盖需要 'override' 关键字
}
}
strictNullChecks:最重要的一个
// 没有 strictNullChecks:
function getLength(s: string) {
return s.length // 没问题……但 s 可能是 null!
}
getLength(null) // TypeScript 说 ✅,运行时说 ❌
// 启用 strictNullChecks:
function getLength(s: string) {
return s.length
}
getLength(null) // ❌ TypeScript 错误:null 不能赋值给 string
// 现在你必须处理 null 的情况:
function getLength(s: string | null): number {
if (s === null) return 0
return s.length
}
// 或者使用可选链和空值合并:
const length = s?.length ?? 0

使用 strictNullChecks 进行类型收窄
interface User {
id: number
name: string
address?: { // 可选——可能为 undefined
city: string
country: string
}
}
function displayUser(user: User | null) {
// ❌ 没有收窄:
console.log(user.name) // 错误:user 可能为 null
console.log(user.address.city) // 错误:user.address 可能为 undefined
// ✅ 正确收窄:
if (!user) return
console.log(user.name) // ✅ 此处 user 为 User
// address 仍然是可选的
console.log(user.address?.city) // ✅ 可选链:string | undefined
console.log(user.address?.city ?? 'Unknown') // ✅ 非空:string
// 通过解构收窄:
const { address } = user
if (address) {
console.log(address.city) // ✅ 此处 address 为 { city: string, country: string }
}
}
noImplicitAny:不再隐藏类型错误
// ❌ 没有 noImplicitAny——以下代码编译通过:
function processData(data) { // data: any(隐式)
return data.someProperty.nested.value // 没有类型检查!
}
// ✅ 启用 noImplicitAny——必须显式声明类型:
function processData(data: ApiResponse) { // 必须显式
return data.result.items[0].name // 类型检查通过!
}
// 当你确实不知道类型时(外部数据):
function processUnknown(data: unknown) {
// unknown 强制你在使用前进行收窄
if (typeof data === 'object' && data !== null && 'name' in data) {
console.log((data as { name: string }).name)
}
}
// 对于逃生舱口,要显式声明:
const value = JSON.parse(rawJson) as ApiResponse // 显式转换
// 或者使用 zod 进行运行时验证:
const result = ApiResponseSchema.parse(JSON.parse(rawJson))

strictPropertyInitialization
// ❌ 没有 strict——编译通过但运行时崩溃:
class UserService {
db: Database // 声明但从未赋值!
findUser(id: number) {
return this.db.query(id) // RuntimeError: cannot read 'query' of undefined
}
}
// ✅ 启用 strict——三种修复方式:
// 选项 1:在构造函数中初始化
class UserService {
db: Database
constructor(db: Database) {
this.db = db // 在构造函数中赋值
}
}
// 选项 2:内联初始化
class UserService {
db: Database = new MockDatabase()
}
// 选项 3:确定赋值断言(当你确定在其他地方已初始化时)
class UserService {
db!: Database // '!' 告诉 TypeScript “我知道已赋值,相信我”
initialize(db: Database) {
this.db = db
}
}
noUncheckedIndexedAccess:数组安全
// 没有 noUncheckedIndexedAccess:
const arr = [1, 2, 3]
const first: number = arr[0] // TypeScript 说 number,但可能为 undefined!
// 启用 noUncheckedIndexedAccess:
const arr = [1, 2, 3]
const first = arr[0] // 类型:number | undefined
// 现在你必须处理 undefined 的情况:
if (first !== undefined) {
console.log(first * 2)
}
// 或者断言非空(谨慎使用):
const first = arr[0]! // 断言非空
// 更好的做法:使用安全访问模式
const first = arr.at(0) // 返回 T | undefined——与启用 noUncheckedIndexedAccess 的 arr[0] 相同
const sum = arr.reduce((acc, n) => acc + n, 0) // 无需索引访问
// 对象索引签名:
interface Config {
[key: string]: string
}
const config: Config = { debug: 'true' }
const value = config['debug'] // 启用 noUncheckedIndexedAccess:string | undefined
增量迁移到严格模式
不要试图在大型现有代码库中一次性启用严格模式——错误数量会让人不知所措。请逐步启用标志:
// tsconfig.json——从影响最大的开始
{
"compilerOptions": {
// 阶段 1:从这里开始(最容易见效)
"noImplicitAny": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
// 阶段 2:最重要的一个
"strictNullChecks": true,
// 阶段 3:额外的严格性
"strictFunctionTypes": true,
"strictPropertyInitialization": true,
"useUnknownInCatchVariables": true,
// 阶段 4:最大严格性
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true
}
}
技巧:使用带跟踪注释的 ts-ignore 抑制
// 当无法立即修复错误时,标记为待办:
// @ts-expect-error TODO(2026-07-01): 在 UserService 重构后修复
const value = legacyUserService.getUnsafeData()
// ts-expect-error 优于 ts-ignore:
// - 如果错误消失,ts-expect-error 会报错(强制你移除它)
// - ts-ignore 即使错误已修复也会静默保留
启用严格后的常见模式
// 模式 1:类型守卫
function isUser(value: unknown): value is User {
return (
typeof value === 'object' &&
value !== null &&
'id' in value &&
typeof (value as any).id === 'number' &&
'name' in value &&
typeof (value as any).name === 'string'
)
}
// 模式 2:断言函数
function assertDefined<T>(value: T | null | undefined, message?: string): asserts value is T {
if (value === null || value === undefined) {
throw new Error(message ?? 'Expected value to be defined')
}
}
const user = getUser(id) // User | null
assertDefined(user, 'User not found')
console.log(user.name) // ✅ TypeScript 知道此处 user 是 User
// 模式 3:穷举 switch
类型 Status = 'pending' | 'active' | 'inactive'
function displayStatus(status: Status): string {
switch (status) {
case 'pending': return 'Awaiting approval'
case 'active': return 'Active'
case 'inactive': return 'Inactive'
default:
// 如果你添加新的 Status 值,这里会变成类型错误
const _exhaustive: never = status
throw new Error(`Unhandled status: ${_exhaustive}`)
}
}
// 模式 4:可辨识联合类型替代 any
type ApiResult<T> =
| { status: 'success'; data: T }
| { status: 'error'; error: string; code: number }
| { status: 'loading' }
function handleResult<T>(result: ApiResult<T>) {
switch (result.status) {
case 'success':
console.log(result.data) // ✅ TypeScript 知道此处 data 存在
break
case 'error':
console.log(result.error, result.code) // ✅ TypeScript 知道 error/code 存在
break
case 'loading':
console.log('Loading...') // 无法访问 data 或 error
break
}
}
严格模式 TypeScript 是前端代码库中投资回报率最高的项目之一。修复类型错误的前期成本是真实但有限的。而持续的好处——消除生产环境中的“Cannot read properties of undefined”——会无限累积。
→ 使用 Text Statistics 工具分析文本内容统计(字数、字符数)。