這篇文章主要介紹“Angular變更檢測中的DOM更新機制是什么”,在日常操作中,相信很多人在Angular變更檢測中的DOM更新機制是什么問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Angular變更檢測中的DOM更新機制是什么”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
變更檢測是Angular中很重要的一部分,也就是模型和視圖之間保持同步。在日常開發過程中,我們無需了解變更檢測,因為Angular都幫我們完成了這一部分工作,讓開發人員更加專注于業務實現,提高開發效率和開發體驗。但是如果想要深入使用框架,或者想要寫出高性能的代碼而不僅僅只是實現了功能,就必須要去了解變更檢測,它可以幫助我們更好的理解框架,調試錯誤,提高性能等。
我們先來看一個小例子。
當我們點擊按鈕的時候,改變了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(),檢查一下有沒有變化,有變化則會拋錯(后面會詳細說這個問題,暫時跳過)。
那好了,現在也知道怎么能讓它更新了,就是要調用一下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();
}
}
果然,可以正常的更新視圖了。
到此,關于“Angular變更檢測中的DOM更新機制是什么”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。