首頁 > web前端 > js教程 > 淺析Angular變更偵測中的DOM更新機制

淺析Angular變更偵測中的DOM更新機制

青灯夜游
發布: 2022-12-12 20:21:15
轉載
2599 人瀏覽過

淺析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 {
    /** @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中文網其他相關文章!

來源:juejin.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
最新問題
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板