当你在 tsconfig.json 中将
"strict": false
时,TypeScript 会关闭一组"严格类型检查"规则。这样虽然可以更快上手、减少编译报错,但也会让更多潜在的运行时错误躲过编译期,降低代码可维护性和重构安全性。下面先解释 strict 的概念与包含的子选项,再通过通俗示例展示关闭 strict 可能带来的问题,以及相应的"正确写法"。
strict 是什么?
在 TypeScript 中,"strict": true
会一次性启用一组严格相关的检查选项(具体集合随 TS 版本略有变化,一般包括):
"noImplicitAny"
:禁止隐式的any
。"strictNullChecks"
:严格区分可能为null
/undefined
的值。"strictFunctionTypes"
:更严格地检查函数类型参数的协变/逆变。"strictBindCallApply"
:严格检查call
/apply
/bind
的参数类型。"strictPropertyInitialization"
:类的实例属性必须初始化或在构造函数中赋值。"useUnknownInCatchVariables"
:catch (e)
中的e
默认为unknown
,需要显式缩小或断言。"noImplicitThis"
:禁止隐式的this:any
。"alwaysStrict"
:编译后的 JS 使用严格模式("use strict"),非模块脚本场景更明显。
注意:
- 有些常用但不在 strict 集合内的"更严"选项(如
"noUncheckedIndexedAccess"
、"exactOptionalPropertyTypes"
)需要手动单独开启。 - 严格集合可能随 TS 版本演进略有增减,以本地 TypeScript 版本文档为准。
关闭 strict 的总体影响
- 更多隐式的
any
混入,类型信息丢失,重构风险上升。 null
/undefined
不再被严格检查,出现空指针类错误的概率增加。- 函数的参数与回调更容易"误配",问题延迟到运行时才爆炸。
- 类的属性可能未初始化即被访问,导致不可预期的
undefined
。 catch (e)
直接当成any
使用,降低异常处理的安全性与可读性。- 运行时严格模式的约束(在非模块脚本里)可能缺失,某些 this 绑定问题更隐蔽。
常见场景与示例
以下示例中的"错误示例"指的是:strict 关闭时能编译通过、但存在潜在运行时风险或类型不安全;"正确示例"是符合 strict 的写法或修复方式。
1、noImplicitAny:隐式 any
错误示例(strict 关闭时编译通过,风险大):
sql
function add(a, b) {
// a 和 b 被推断为 any,"数字相加"变成了"任意拼接"
return a + b;
}
add(1, "2"); // 运行结果是 "12",并非 3
正确示例(显式标注或提供默认值/约束):
less
function add(a: number, b: number) {
return a + b;
}
add(1, 2); // 3
参考:
- www.typescriptlang.org/tsconfig#no...
- 手册"Everyday Types"关于 any 的说明
2、strictNullChecks:空值检查
- 作用:将 null、undefined 从所有类型中分离出来,必须显式处理"可能为空"的情况。
- 影响:开启后可显著减少空指针错误,但需要在代码中做空值校验或使用可选链等。
错误示例
typescript
function greet(name: string) {
// 如果实际传的是 undefined,这里会在运行时报错
console.log(name.toUpperCase());
}
greet(undefined as any); // 运行期 TypeError
正确示例(显式包含可能的空值并做保护):
javascript
function greet(name: string | undefined) {
if (!name) {
console.log("Hello, stranger!");
return;
}
console.log(name.toUpperCase());
}
或改为不允许空值
scss
function greet(name: string) {
console.log(name.toUpperCase());
}
// 调用方自己保证非空
greet("Alice");
参考:
- www.typescriptlang.org/tsconfig#st...
- 手册 Null 与 Undefined 章节(Everyday Types / Narrowing)
3、 strictPropertyInitialization:属性初始化
错误示例(strict 关闭时编译通过,但属性为 undefined):
typescript
class User {
name: string; // 未初始化
constructor() {
// 忘了给 name 赋值
}
}
const u = new User();
u.name.toUpperCase(); // 运行期 TypeError
正确示例(在声明处或构造函数中初始化,或使用确定赋值断言):
typescript
// 方式一:声明处初始化
class User {
name: string = "anonymous";
}
// 方式二:构造函数中初始化
class User2 {
name: string;
constructor(name: string) {
this.name = name;
}
}
// 方式三:确定赋值断言(慎用,需要你自己保证在使用前已赋值)
class User3 {
name!: string;
init(name: string) {
this.name = name;
}
}
参考:
www.typescriptlang.org/tsconfig/#s...
4、strictBindCallApply:call/apply 的参数检查
错误示例(strict 关闭时编译通过,运行期可能异常):
typescript
function sum(a: number, b: number): number {
return a + b;
}
sum.call(null, 1, "2" as any); // 编译不报错,但语义错误
正确示例(严格匹配参数类型):
typescript
function sum(a: number, b: number): number {
return a + b;
}
sum.call(null, 1, 2); // OK
参考:
www.typescriptlang.org/tsconfig/#s...
5、strictFunctionTypes:函数类型的参数检查更严格
错误示例(strict 关闭时可能被接受,存在类型不安全):
typescript
interface Animal { name: string }
interface Dog extends Animal { bark(): void }
type Handler = (a: Animal) => void;
// 将参数更"窄"的函数赋给更"宽"的函数类型(不安全)
const handleDog: (d: Dog) => void = d => d.bark();
// 在 strictFunctionTypes 关闭时,这样的赋值可能被允许
const h: Handler = handleDog;
// 运行时:如果传入的是普通 Animal 而非 Dog,会出错
const justAnimal: Animal = { name: "Tom" };
h(justAnimal as any); // 运行期可能调用 bark() 报错
正确示例(使用恰当的参数范围或做类型保护):
javascript
const handleAnimal: Handler = a => {
// 若需要 Dog 的行为,先做类型缩小
if ((a as Dog).bark) {
(a as Dog).bark();
} else {
console.log(a.name);
}
};
或改变设计:将"输出协变、输入逆变"的原则体现到类型中,避免将"窄参数函数"赋给"宽参数函数"。
参考:
www.typescriptlang.org/tsconfig/#s...
6、useUnknownInCatchVariables:异常类型安全
错误示例(strict 关闭时 e 是 any,使用随意):
javascript
try {
throw new Error("oops");
} catch (e) {
// e 是 any,随意访问属性,潜在不安全
console.log(e.message.toUpperCase());
}
正确示例(将 e 视为 unknown,先缩小/断言):
javascript
try {
throw new Error("oops");
} catch (e) {
if (e instanceof Error) {
console.log(e.message.toUpperCase());
} else {
console.log("Unknown error", e);
}
}
参考:
www.typescriptlang.org/tsconfig/#u...
7、noImplicitThis:避免 this:any
错误示例(strict 关闭时不报错,运行期 this 可能是 undefined 或全局对象):
javascript
function getX() {
// 在非严格模式下,独立调用时 this 可能指向全局;在严格模式下为 undefined
return (this as any).x;
}
const obj = { x: 42, getX };
const fn = obj.getX;
console.log(fn()); // 结果依赖调用环境,容易出错
正确示例(显式 this 类型或用箭头函数绑定):
javascript
function getX(this: { x: number }) {
return this.x;
}
const obj = { x: 42, getX };
console.log(obj.getX()); // 42
或者:
ini
const obj2 = {
x: 42,
getX: () => 42, // 不依赖 this
};
参考:
www.typescriptlang.org/tsconfig/#n...
8、alwaysStrict:输出严格模式
在非模块脚本中,关闭 strict 也可能意味着编译产物不含 "use strict"。这会影响诸如 this
的指向、静默失败等行为,增加与运行时相关的隐患。使用模块(ESM/CJS)时通常天然是严格模式,但仍建议开启 alwaysStrict
以保持一致性。
什么时候可以考虑关掉 strict?
- 原有 JavaScript/TS 项目体量大、类型债务多,短期无法一次性治理。
- 第三方声明文件质量参差、阻塞开发进度时,临时"松绑"。
即便如此,也建议:
- 尽量只临时关闭,或仅关闭阻塞你当前工作的个别子选项,而不是整个
"strict": false
。 - 在关键模块、核心域模型里保持严格。
- 逐步还债:为公共 API、数据边界(网络、存储、DOM)补上类型。
渐进式启用的建议
-
打开
"strict": true
,如果报错太多,逐个关闭必要的子选项,再计划性回收:- 先解决
"noImplicitAny"
和"strictNullChecks"
(收益最大)。 - 再处理
"strictPropertyInitialization"
、"noImplicitThis"
等。
- 先解决
-
配合 ESLint(typescript-eslint)规则,保持一致的代码风格与安全约束。
-
在项目中引入额外的安全选项(即便不在 strict 内):
"noUncheckedIndexedAccess": true
(索引访问返回T | undefined
)"exactOptionalPropertyTypes": true
(可选属性更精确)
-
对外部输入做运行时校验(zod、io-ts),让类型安全与数据校验形成闭环。
总结
- 关闭
"strict"
会让 TypeScript 更宽松、上手更快,但潜在运行时错误更容易漏网,重构成本和风险上升。 - 严格模式能在"写代码"而不是"跑起来"时暴露问题,长期能显著提升代码质量与信心。
- 如果历史原因不得不开启宽松模式,也应规划渐进式回到严格,至少保证关键路径与公共接口的类型安全。