angular11源码探索二十四[路由检测变更策略]
NgZone
Angular为我们提供了NgZone服务,对于一些频繁的操作,可以不去触发变更检测。工作时优化性能
变更检测
Events
- 一些事件,例如click
、change
、input
、submit
等;XMLHttpRequests
- 网络请求;Timers
-setTimeout()
与setInterval()
API 等;
每次变更检测都意味着额外的计算和资源消耗,如果我们需要对应用进行性能优化,那么首先该从这个概念下手。Angular 引入 Zone.js 以处理变更检测,具体来说,Zone.js 通过对所有常见的异步 API 打上了“补丁” 以追踪所有的异步操作,进而使 Angular 可以决定何时刷新 UI。
Zone.js
- Zone 是一种用于拦截和跟踪异步工作的机制。
- 在 Angular 应用中,每个 task 都会在 “Angular” Zone 中运行,这个 Zone 被称为 NgZone。一个 Angular 应用中只存在一个 Angular Zone,而变更检测只会由 运行于这个 NgZone 中的异步操作触发。
示例
函数runOutsideAngular
用于确保代码中在NgZone之外运行,保证Angular的变更检测不会因为相关代码而触发
setInterval
定时器便不会触发变更检测
export class OneComponent implements OnInit {
constructor(
private ngZone: NgZone
) {
this.ngZone.runOutsideAngular(() => {
setInterval(() => {
++this.num;
}, 1000);
});
}
num = 1;
}
我们发现因为变更检测没有出发,所以视图没有更新
<h1>{{num}}</h1>
run
方法的目的与 runOutsideAngular
正好相反:任何写在 run 里的方法,都会进入 Angular Zone 的管辖范围
this.ngZone.runOutsideAngular(() => {
const token = setInterval(() => {
this.ngZone.run(() => {
++this.num;
});
if (this.num == 10) {
clearInterval(token);
}
}, 1000);
});
num = 1;
我们发现页面更新啦,
其实这是我在源码单元测试看到的,疑惑查了下,感觉在写大屏的时候可以使用的
如果我们有一个一定时间后更新一个值
this.ngZone.runOutsideAngular(() => {
const token = setTimeout(() => {
this.ngZone.run(() => {
this.num = 10;
});
clearTimeout(token);
}, 5000);
});
ApplicationRef
tick
调用此方法可显式处理更改检测及其副作用。
export class OneComponent implements OnInit {
constructor(
private ngZone: NgZone,
private app: ApplicationRef
) {
this.ngZone.runOutsideAngular(() => {
const token = setTimeout(() => {
this.num = 100;
//调用更新检测
app.tick();
clearTimeout(token);
}, 3000);
});
}
}
我们发现上面的案例更加适用于NgZone
那我们可不可以从脏依赖的角度上来看
@Component({
selector: "hello",
template: `
<h1>Hello {{ name }}!</h1>
<p>{{ counter }}</p>
<button (click)="click()">click</button>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class HelloComponent {
counter: number = 0;
@Input() name: string;
constructor(private app: ApplicationRef) {
setInterval(() => this.counter++, 1000);
}
click() {
this.app.tick();
}
}
markForCheck()、OnPush、zone.js与detectChanges()
Angular 更新 HTML 的原理
- 初始化 Component 。在最开始 bootstrapping Angular Application 的时候,Angular 会载入 bootstrap component 并调用
ApplicationRef.tick()
方法来触发 变更策略检测 - 监听事件,如果DOM的事件更新了 Angular component 里面的data, 那么 变更策略检测 触发
- HTTP 数据请求。如果在 Angular component 里更新了请求的数据
- 宏任务微任务 ,方法里更新了 Angular component 的数据。比如
setTimeout()
,setInterval()
。Promise.then()
里更新了 Angular component 的数据
zone.js
Angular 自带了 ngZone
的 service,这个 service 会创建一个叫 ‘angular’ 的 Zone。在这个 Angular Zone 里,当没有 schedule MicroTasks 或是当执行 sync/async function 的时候会自动触发 change detection。如果 Angular 的检测策略是的 default
的,那么所有的 asynchronous operations 都是在 Angular Zone 里,并且都会按以上条件自动触发 change detection。
不触发
this.ngZone.runOutsideAngular(() => {
不触发
});
this.ngZone.run(() => {
触发
});
detectChanges()
当这个第三方 APIs 的方法 update data 的时候,这个 update 是在 Angular Zone 之外的,所以 Angular 并不会知道这个 update 的发生,因此也不会触发变更策略检测。在这个情况下,就可以调用 ChangeDetectorRef.detectChanges()
来手动触发变更策略检测 也可以放在zone.run()
方法
关于 OnPush
和 markForCheck()
如果 Angular 的检测策略被设置为 OnPush
,那么只有以下条件下 change detection 才会被触发:
- 当带有
@Input
property 的引用完全变化或是完全被新的值替换的时候。 - 当当前 component 或是 child component 触发 event 的时候,例如 Click,KeyUp,Subscription 等等。
很多时候 update 就不会触发变更策略检测。这时就必须通过调用 markForCheck()
来手动告诉 Angular 去检查并更新 view。所以总体来说,markForCheck()
基本上只在 Angular 的检测策略被设置为 OnPush
的时候会被需要使用到。
markForCheck()
和 detectChanges()
的核心区别
最后,总结一下 markForCheck()
和 detectChanges()
的核心区别。detectChanges()
会真正触发 Angular 的 change detection,而 markForCheck()
则不会。
当我们禁用NoopZone
Zone
帮助Angular知道何时触发更改检测,并使开发人员专注于应用程序开发。默认情况下,Zone
已加载且无需额外配置即可工作。但是,您不必一定要使用Zone
Angular。相反,您可以选择自己触发更改检测。
要删除Zone.js,请进行以下更改。
-
zone.js
从中删除导入polyfills.ts
:// import 'zone.js/dist/zone'; // Included with Angular CLI.`
-
Bootstrap Angular的
noop
区域位于src/main.ts
:platformBrowserDynamic().bootstrapModule(AppModule, { ngZone: 'noop' }) .catch(err => console.error(err));
思考
删除Zone.js 让我们获得更多性能优势,如果一切都是ChangeDetectionStrategy.OnPush
则永远不会触发更改检测周期,除非通过下面
ChangeDetectorRef.detectChanges()
ApplicationRef.tick()
注意一个易错点ChangeDetectorRef.markForCheck
,它仅设置组件的视图层,而不会触发更改检测周期
决定自己的高度的是你的态度,而不是你的才能
记得我们是终身初学者和学习者
总有一天我也能成为大佬