在 Angular 中,我们坚信一致性和最佳实践。例如,我们在早期采用了 TypeScript ,因为我们希望所有使用该框架的开发人员都能利用编译时类型检查。通过这种方式,我们通过更好的编辑器支持为每个人提供了出色的开发体验,使人们能够在发布应用程序时出现更少的问题。
在v10中,我们宣布了一种实验性严格选择模式,该模式使我们能够执行更多构建时优化,并帮助您交付更快、缺陷更少的应用程序。
从长远来看,我们希望启用最严格的标志来鼓励最佳实践,同时不会显着影响框架入门时的学习曲线,也不会影响 DX。我们使用严格模式来鼓励采用最佳实践,并了解开发人员会感到满意的严格选项,同时仍然启用大多数编译时正确性检查和优化。
我们希望听取您的意见,这些权衡中哪些可以提高您的工作效率,并让您能够交付更好的应用程序。
要选择进入严格模式,您需要创建一个新的 Angular CLI 应用程序,并指定--strict
标志:
ng new my-app --strict
上面的命令将生成一个工作区,在默认设置的基础上启用以下设置:
strict
, forceConsistentCasingInFileNames
、 noImplicitReturns
、 noFallthroughCasesInSwitch
strictTemplates
和strictInjectionParameters
no-any
TSLint 规则以防止声明any
类型让我们详细看看其中的每一项。
更严格的类型检查
研究人员凭经验证明,TypeScript 的编译器可以帮助我们在将应用程序投入生产之前解决更多问题。有关更多详细信息,请查看“键入或不键入:量化 JavaScript 中可检测到的错误”。为了不妨碍人们开始使用 Angular,默认情况下,我们目前不会在新的 CLI 工作区中启用最严格的 TypeScript 编译器选项。
为了从编译器获得更多帮助,许多使用 Angular 的开发人员要做的第一件事就是在tsconfig.json
中启用以下标志:
这样,他们就可以更自信地交付生产,因为编译器为他们提供了一些保证,确保他们的应用程序符合类型系统定义的特定约定。在 CLI 的严格模式下,我们启用TypeScript 的s trict
标志,默认情况下会打开这些标志。
对开发过程影响最大的两个标志是strictPropertyInitialization
和strictNullChecks
。使用strictPropertyInitialization
,以下代码片段将引发编译时错误,因为我们尚未初始化title
:
@Component({...})
class AppComponent {
@Input() title: string;
}
要修复它,您必须将title
设置为字符串值或更改其类型,例如:
@Component({...})
class AppComponent {
@Input() title = '';
}
我们启用的一项附加检查是no-any
TSLint 规则(是的,我们将放弃 TSLint ;该规则具有其ESLint 等效项)。这样,我们可以在ng update
命令期间使用更多类型信息,并更自信地跨 Angular 版本迁移应用程序。让我们看一个例子:
import { HttpClient } from '@angular/common/http';
@Component({...})
export class AppComponent {
client: any;
constructor(http: HttpClient) {
this.client = http;
}
handleClick() {
this.client.deprecatedMethod();
}
}
如果我们想将您的项目从deprecatedMethod
中迁移出来,我们将无法考虑到client
的类型是any
。为了放心地迁移您的项目,我们需要client
具有HttpClient
类型并带有显式类型注释的信息: client: HttpClient
。这样我们就知道deprecatedMethod
属于我们的迁移目标,而不是仅仅碰巧另一个对象具有同名的方法。
strictTemplates
标志是 Angular 特定配置,严格模式在tsconfig.json
中的angularCompilerOptions
下启用。让我们看一下下面的代码片段:
<div *ngFor="let todo of todos">
<h2>{{ todo.title }}</h2>
<button *ngIf="user.isAdmin">Edit</button>
</div>
目前,Angular 不会在编译时执行任何类型检查。如果启用strictTemplates
,Angular 将检查todo
是否有属性title
以及user.isAdmin
是否存在。此外,如果启用了任何严格的 TypeScript 标志,Angular 编译器将为模板启用相同的严格性检查。例如,如果下面的代码片段中有strictNullChecks
和strictTemplates
,我们将收到类型错误:
@Component({
template: '<h1>{{ article.title }}</h1>'
})
class ArticleComponent {
article: Article | undefined;
}
这将帮助我们确保在访问文章的属性之前已经初始化了article
。修复此错误的方法是: <h1>{{ article?.title }}</h1>
使用安全导航运算符(类似于可选链接)。
权衡:
strictNullChecks
和strictPropertyInitialization
将确保您不会访问空引用的属性或调用方法。捆绑预算可以保证您的应用程序的性能不会随着时间的推移而明显下降。在 Angular CLI 的angular.json
文件中,我们预定义了错误和警告预算。如果特定 JavaScript 包或样式的大小超出警告预算,您将收到警告消息;如果超出错误预算,您的构建将会失败。
默认情况下,我们设置了相当高的预算:
在严格模式下,这些数字降至:
为了确保您符合预算:
权衡:
减少副作用
我们将看到的最后一个严格模式选项与树摇动有关。 Tree-shaking 是一种死代码消除形式,其中构建工具会删除未使用的代码。为了使 webpack 能够删除未使用的模块,在严格模式下,我们使用所有应用程序和库创建一个额外的package.json
。此package.json
文件有一个属性 – sideEffects
设置为false
。
在像 JavaScript 这样的动态语言中,通过静态代码分析几乎不可能确定模块是否产生副作用。这意味着我们的乐观假设可能会破坏您的应用程序。让我们更详细地了解这实际上意味着什么。
目前,Angular CLI webpack 将 tree-shaking 委托给我们使用的 JavaScript 优化器terser 。让我们看一个例子:
// constants.js
export const PI = 3.14159265357989;
// index.js import { add } from './utils'; const subtract = (a, b) => a - b; console.log(add(1, 2));
// operations.js export const add = (a, b) => a + b; export const subtract = (a, b) => a - b;
// utils.js
export * from './operations';
export * from './constants';
这里我们有以下依赖图:
当我们使用入口点index.js
运行 webpack 时,我们将得到以下输出:
/******/ (() => { // webpackBootstrap
/******/ "use strict";
// CONCATENATED MODULE: ./operations.js
// utils.js
const add = (a, b) => a + b;
const subtract = (a, b) => a - b;
// CONCATENATED MODULE: ./constants.js
const PI = 3.14159265357989;
// CONCATENATED MODULE: ./utils.js
// CONCATENATED MODULE: ./index.js
// index.js
const index_subtract = (a, b) => a - b;
console.log(add(1, 2));
/******/ })();
请注意,虽然我们不使用subtract
和PI
,但它们仍然在最终包中。如果我们在生产模式下运行 webpack,terser 将统计确定我们仅使用add
,并且它将删除导出的subtract
和PI
。
webpack 在最终包中包含模块constants.js
只是因为不确定它是否会产生副作用。例如,如果它添加全局变量、写入localStorage
或发送 HTTP 请求,则删除此模块将破坏我们的应用程序。
假设我们在项目对应的package.json
中将sideEffects
属性指定为false
。在这种情况下,webpack 将假设utils.js
、 constants.js
和operations.js
不会产生任何副作用,并且不会在最终包中包含未使用的模块。在这种情况下,最终的捆绑包如下所示:
/******/ (() => { // webpackBootstrap
/******/ "use strict";
// CONCATENATED MODULE: ./utils.js
// utils.js
const add = (a, b) => a + b;
const subtract = (a, b) => a - b;
// CONCATENATED MODULE: ./index.js
// index.js
const index_subtract = (a, b) => a - b;
console.log(add(1, 2));
/******/ })();
如果我们启用terser,它将摆脱index_subtract
和subtract
因为我们不使用它们,并且内联add
,这将产生:
(()=>{"use strict";console.log(1+2)})();
权衡
package.json
文件 – 一个用于您的工作区,一个用于您的每个库和应用程序。这可能会让刚开始使用 Angular CLI 的人感到困惑。让我们花一点时间来看看第二点。想象constants.js
有以下内容:
// constants.js
export const PI = 3.14159265357989;
localStorage.setItem('foo', 'bar');
如果我们错误地将sideEffects
标志指定为false
并且 webpack 会删除这个模块,因为我们没有引用它, localStorage.setItem('foo', 'bar')
语句不会执行。如果我们依赖localStorage
中的这些信息,那么稍后可能会导致我们的应用程序出现问题。
到目前为止我们提到的一切都有其权衡。如果我们想要减少生产中的问题,我们需要使用更严格的编译器选项,如果我们想要更小的捆绑包,我们可能必须启用额外的、可能不安全的配置。
请在这篇文章中发表评论,让我们知道您希望我们默认启用其中哪一个配置选项以及原因。我们正在为您构建 Angular CLI,我们希望您参与我们提供哪些功能的决策过程。
文章来源地址:https://blog.angular.dev/angular-cli-strict-mode-c94ba5965f63