• 技术文章 >web前端 >js教程

    浅析Angular变更检测中的DOM更新机制

    青灯夜游青灯夜游2022-12-12 20:21:15转载91

    大前端零基础入门到就业:进入学习

    变更检测是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 {
        /** @internal */
        constructor(_zone, _injector, _exceptionHandler, _initStatus) {
            this._zone = _zone;
            this._injector = _injector;
            this._exceptionHandler = _exceptionHandler;
            this._initStatus = _initStatus;
            /** @internal */
            this._bootstrapListeners = [];
            this._views = [];
            this._runningTick = false;
            this._stable = true;
            this._destroyed = false;
            this._destroyListeners = [];
            /**
             * Get a list of component types registered to this application.
             * This list is populated even before the component is created.
             */
            this.componentTypes = [];
            /**
             * Get a list of components registered to this application.
             */
            this.components = [];
            this._onMicrotaskEmptySubscription = this._zone.onMicrotaskEmpty.subscribe({
                next: () => {
                    this._zone.run(() => {
                        this.tick();
                    });
                }
            });
            ...
        }
    
    /**
         * Invoke this method to explicitly process change detection and its side-effects.
         *
         * In development mode, `tick()` also performs a second change detection cycle to ensure that no
         * further changes are detected. If additional changes are picked up during this second cycle,
         * bindings in the app have side-effects that cannot be resolved in a single change detection
         * pass.
         * In this case, Angular throws an error, since an Angular application can only have one change
         * detection pass during which all change detection must complete.
         */
        tick() {
            NG_DEV_MODE && this.warnIfDestroyed();
            if (this._runningTick) {
                const errorMessage = (typeof ngDevMode === 'undefined' || ngDevMode) ?
                    'ApplicationRef.tick is called recursively' :
                    '';
                throw new RuntimeError(101 /* RuntimeErrorCode.RECURSIVE_APPLICATION_REF_TICK */, 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(),检查一下有没有变化,有变化则会抛错(后面会详细说这个问题,暂时跳过)。

    那好了,现在也知道怎么能让它更新了,就是要调用一下ApplicationReftick方法。

    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更新机制的详细内容,更多请关注php中文网其它相关文章!

    声明:本文转载于:掘金社区,如有侵犯,请联系admin@php.cn删除

    前端(VUE)零基础到就业课程:点击学习

    清晰的学习路线+老师随时辅导答疑

    自己动手写 PHP MVC 框架:点击学习

    快速了解MVC架构、了解框架底层运行原理

    上一篇:一文带你深入了解Node中的Buffer类 下一篇:自己动手写 PHP MVC 框架(40节精讲/巨细/新人进阶必看)

    相关文章推荐

    • ❤️‍🔥共22门课程,总价3725元,会员免费学• ❤️‍🔥接口自动化测试不想写代码?• 深入了解Angular中的NgModule(模块)• 如何在Angular service中使用TemplateRef• Angular学习之聊聊notification(自定义服务)• Angular学习之详解样式绑定(ngClass和ngStyle)的使用• Angular开发问题记录:组件数据不能实时更新到视图上
    1/1

    PHP中文网