NgZone:runOutsideAngular和run结合使用,减少变化检测,提高性能

NgZone

一种用于在 Angular Zone 内部或外部执行任务的可注入服务。

Zone.js 提供了一种称为区域(Zone)的机制,用于封装和拦截浏览器中的异步活动。

最常见的用途是在启动包含一个或多个不需要 Angular 处理的 UI 更新或错误处理的异步任务的工作时优化性能。

runOutsideAngular()

通过 runOutsideAngular() 运行函数可让你离开 Angular 的 Zone 并执行不会触发 Angular 变更检测或受 Angular 错误处理控制的工作。

此函数中计划的任何将来的任务或微任务将在 Angular Zone 之外继续执行。

使用 run() 重新进入 Angular Zone 并执行更新应用程序模型的工作。

run()

通过 run 运行的函数可让你从在 Angular Zone 之外执行的任务(通常通过 runOutsideAngular 启动)重新进入 Angular Zone 。

此函数中计划的任何将来的任务或微任务将在 Angular Zone 内继续执行。

如果发生同步错误,它将被重新抛出,并且不会通过 onError 报告。

应用:减少变化检测次数,提高性能

Angular变化检测只会由运行于 NgZone 中的异步操作触发。会触发变化检测的行为有:
(1) 任何浏览器事件,比如click、keydown等。
(2) setInterval() 、setTimeout
(3) http请求

示例:
由于每次的keydown都会触发Angular的变化检测,而我们该示例中只需要按下Enter键时执行变化检测。

为了避免没有必要的变化检测,提高性能,我们将keydow事件包裹在runOutsideAngular中,让keydown事件执行在Zone之外;当按下的是Enter键时,再通过run方法重新回到Zone中即可。

@Directive({
    selector: '[enter]'
})
export class MyEnterDirective implements OnInit {
    @Output() enter = new EventEmitter();

    constructor(private ngZone: NgZone, private elementRef: ElementRef<HTMLElement>) {}

    ngOnInit(): void {
        this.ngZone.runOutsideAngular(() => {
        	// 点击事件运行在Zone之外
            this.elementRef.nativeElement.addEventListener('keydown', (event: KeyboardEvent) => {
                const keyCode = event.which || event.keyCode;
                if (keyCode === 13) {
                    // 当点击的是enter键时,再通过run重新进入Zone中
                    this.ngZone.run(() => {
                        this.enter.emit(event);
                    });
                }
            });
        });
    }
}

示例

// The example comes from the Angular official website
import {Component, NgZone} from '@angular/core';
import {NgIf} from '@angular/common';

@Component({
  selector: 'ng-zone-demo',
  template: `
    <h2>Demo: NgZone</h2>

    <p>Progress: {{progress}}%</p>
    <p *ngIf="progress >= 100">Done processing {{label}} of Angular zone!</p>

    <button (click)="processWithinAngularZone()">Process within Angular zone</button>
    <button (click)="processOutsideOfAngularZone()">Process outside of Angular zone</button>
  `,
})
export class NgZoneDemo {
    progress: number = 0;

    label: string;

    constructor(private ngZone: NgZone) {}

    // Loop inside the Angular zone ,so the UI DOES refresh after each setTimeout cycle
    // 在angular Zone区域中循环,所以 UI 在每个 setTimeout 周期后都会刷新
    // 所以此示例中,点击按钮后,页面在 progress=20,40,60,80,100时刷新了,刷新了5次
    processWithinAngularZone() {
        this.label = 'inside';
        this.progress = 0;
        this._increaseProgress(() => console.log('Inside Done!'));
    }

    // Loop outside of the Angular zone,so the UI DOES NOT refresh after each setTimeout cycle
    // 在 Angular Zone区域外循环,所以每次 setTimeout 循环后 UI 不会刷新。
    // 所以此示例中,点击按钮后,页面会刷新两次,第一次是progress=20,第二次是progress=100
    processOutsideOfAngularZone() {
        this.label = 'outside';
        this.progress = 0;
        this.ngZone.runOutsideAngular(() => {
            this._increaseProgress(() => {
                // reenter the Angular zone and display done
                // 重新进入Angular区域并显示完成
                this.ngZone.run(() => {
                    console.log('Outside Done!');
                });
            });
        });
    }

    _increaseProgress(doneCallback: () => void) {
        this.progress += 20;
        console.log(`Current progress: ${this.progress}%`);

        if (this.progress < 100) {
            window.setTimeout(() => {
                console.log('setTimeout了');
                this._increaseProgress(doneCallback);
            }, 10);
        } else {
            doneCallback();
        }
    }

}


版权声明:本文为Kate_sicheng原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。