浅析Angular变更检测中的DOM更新机制
变更检测是Angular中很重要的一部分,也就是模型和视图之间保持同步。在日常开发过程中,我们无需了解变更检测,因为Angular都帮我们完成了这一部分工作,让开发人员更加专注于业务实现,提高开发效率和开发体验。但是如果想要深入使用框架,或者想要写出高性能的代码而不仅仅只是实现了功能,就必须要去了解变更检测,它可以帮助我们更好的理解框架,调试错误,提高性能等。【相关教程推荐:《angular教程》】
Angular的DOM更新机制
我们先来看一个小例子。
当我们点击按钮的时候,改变了name属性,同时DOM自动被更新成新的name值。
那现在有一个问题,如果我改变name的值后,紧接着把DOM中的innerText输出出来,它会是什么值呢?
import { Component, ViewChild, ElementRef } from '@angular/core';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
name = 'Empty';
@ViewChild('textContainer') textContainer: ElementRef;
normalClick(): void {
this.name = 'Hello Angular';
console.log(this.textContainer.nativeElement.innerText);
}
}
你答对了吗?
那这两段代码中到底发生了什么呢?
如果我们用原生JS来编写这段代码,那么点击按钮后的视图肯定不会发生任何变化,而在Angular中却让视图发生了变化,那它为什么会自动把视图更新了呢?这离不开一个叫做zone.js的库,简单来说,它是对发生值改变的事件做了一些处理,这个会在后面的部分详细讲解,这里暂时知道这个就可以了。
如果我不想让这个库做这些处理,Angular还为我们提供了禁用zone.js的方法。
可以在main.ts中设置禁用zone.js。
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule, {
ngZone: 'noop'
})
.catch(err => console.error(err));
当我们禁用zone.js,视图并未发生更新。到源码里找一下视图更新的相关代码。
*/
class ApplicationRef {
constructor(_zone, _injector, _exceptionHandler, _initStatus) {
this._zone = _zone;
this._injector = _injector;
this._exceptionHandler = _exceptionHandler;
this._initStatus = _initStatus;
this._bootstrapListeners = [];
this._views = [];
this._runningTick = false;
this._stable = true;
this._destroyed = false;
this._destroyListeners = [];
this.componentTypes = [];
this.components = [];
this._onMicrotaskEmptySubscription = this._zone.onMicrotaskEmpty.subscribe({
next: () => {
this._zone.run(() => {
this.tick();
});
}
});
...
}
tick() {
NG_DEV_MODE && this.warnIfDestroyed();
if (this._runningTick) {
const errorMessage = (typeof ngDevMode === 'undefined' || ngDevMode) ?
'ApplicationRef.tick is called recursively' :
'';
throw new RuntimeError(101 , errorMessage);
}
try {
this._runningTick = true;
for (let view of this._views) {
view.detectChanges();
}
if (typeof ngDevMode === 'undefined' || ngDevMode) {
for (let view of this._views) {
view.checkNoChanges();
}
}
}
catch (e) {
// Attention: Don't rethrow as it could cancel subscriptions to Observables!
this._zone.runOutsideAngular(() => this._exceptionHandler.handleError(e));
}
finally {
this._runningTick = false;
}
}
}
大致解读一下,这个ApplicationRef是Angular整个应用的实例,在构造函数中,zone(zone库)的onMicrotaskEmpty(从名字上看是一个清空微任务的一个subject)订阅了一下。在订阅里,调用了tick(),那tick里做了什么呢?
思考: 上次说了最好订阅不要放到constructor里去订阅,这里怎么这么不规范呢?
当然不是,上次我们说的是Angular组件里哪些应该放constructor,哪些应该放ngOnInit里的情况。但这里,ApplicationRef人家是一个service呀,只能将初始化的代码放constructor。
在tick函数里,如果发现这个tick函数正在执行,则会抛出异常,因为这个是整个应用的实例,不能递归调用。然后,遍历了所有个views,然后每个view都执行了detectChanges(),也就是执行了下变更检测,什么是变更检测,会在后面详细讲解。紧接着,如果是devMode,再次遍历所有的views,每个view执行了checkNoChanges(),检查一下有没有变化,有变化则会抛错(后面会详细说这个问题,暂时跳过)。
那好了,现在也知道怎么能让它更新了,就是要调用一下ApplicationRef的tick方法。
import { Component, ViewChild, ElementRef, ApplicationRef } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
name = 'Empty';
@ViewChild('textContainer') textContainer: ElementRef = {} as any;
constructor(private app: ApplicationRef){}
normalClick(): void {
this.name = 'Hello Angular';
console.log(this.textContainer.nativeElement.innerText);
this.app.tick();
}
}
果然,可以正常的更新视图了。
我们来简单梳理一下,DOM的更新依赖于tick() 的触发,zone.js帮助开发者无需手动触发这个操作。好了,现在可以把zone.js启用了。
那什么是变更检测呢?继续期待下一篇哦。
以上就是浅析Angular变更检测中的DOM更新机制的详细内容,更多请关注编程网其它相关文章!
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341