温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

Angular变更检测中的DOM更新机制是什么

发布时间:2022-12-13 09:36:36 来源:亿速云 阅读:126 作者:iii 栏目:web开发

这篇文章主要介绍“Angular变更检测中的DOM更新机制是什么”,在日常操作中,相信很多人在Angular变更检测中的DOM更新机制是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Angular变更检测中的DOM更新机制是什么”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

变更检测是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();
  }
}

果然,可以正常的更新视图了。

到此,关于“Angular变更检测中的DOM更新机制是什么”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI