国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

[譯] 關(guān)于 `ExpressionChangedAfterItHasBeenCheckedErro

andong777 / 3043人閱讀

摘要:本文將解釋引起這個錯誤的內(nèi)在原因,檢測機制的內(nèi)部原理,提供導致這個錯誤的共同行為,并給出修復這個錯誤的解決方案。這一次過程稱為。這個程序設(shè)計為子組件拋出一個事件,而父組件監(jiān)聽這個事件,而這個事件會引起父組件屬性值發(fā)生改變。

原文鏈接:Everything you need to know about the ExpressionChangedAfterItHasBeenCheckedError error
關(guān)于 ExpressionChangedAfterItHasBeenCheckedError,還可以參考這篇文章,并且文中有 youtube 視頻講解:Angular Debugging "Expression has changed after it was checked": Simple Explanation (and Fix)

最近 stackoverflow 上幾乎每天都有人提到 Angular 拋出的一個錯誤:ExpressionChangedAfterItHasBeenCheckedError,通常提出這個問題的 Angular 開發(fā)者都不理解變更檢測(change detection)的原理,不理解為何產(chǎn)生這個錯誤的數(shù)據(jù)更新檢查是必須的,甚至很多開發(fā)者認為這是 Angular 框架的一個 bug(譯者注:Angular 提供變更檢測功能,包括自動觸發(fā)和手動觸發(fā),自動觸發(fā)是默認的,手動觸發(fā)是在使用 ChangeDetectionStrategy.OnPush 關(guān)閉自動觸發(fā)的情況下生效。如何手動觸發(fā),參考 Triggering change detection manually in Angular)。當然不是了!其實這是 Angular 的警告機制,防止由于模型數(shù)據(jù)(model data)與視圖 UI 不一致,導致頁面上存在錯誤或過時的數(shù)據(jù)展示給用戶。

本文將解釋引起這個錯誤的內(nèi)在原因,檢測機制的內(nèi)部原理,提供導致這個錯誤的共同行為,并給出修復這個錯誤的解決方案。最后章節(jié)解釋為什么數(shù)據(jù)更新檢查是如此重要。

It seems that the more links to the sources I put in the article the less likely people are to recommend it ?. That’s why there will be no reference to the sources in this article.(譯者注:這是作者的吐槽,不翻譯)

相關(guān)變更檢測行為

一個運行的 Angular 程序其實是一個組件樹,在變更檢測期間,Angular 會按照以下順序檢查每一個組件(譯者注:這個列表稱為列表 1):

更新所有子組件/指令的綁定屬性

調(diào)用所有子組件/指令的三個生命周期鉤子:ngOnInitOnChangesngDoCheck

更新當前組件的 DOM

為子組件執(zhí)行變更檢測(譯者注:在子組件上重復上面三個步驟,依次遞歸下去)

為所有子組件/指令調(diào)用當前組件的 ngAfterViewInit 生命周期鉤子

在變更檢測期間還會有其他操作,可以參考我寫的文章:《Everything you need to know about change detection in Angular》

在每一次操作后,Angular 會記下執(zhí)行當前操作所需要的值,并存放在組件視圖的 oldValues 屬性里(譯者注:Angular Compiler 會把每一個組件編譯為對應(yīng)的 view class,即組件視圖類)。在所有組件的檢查更新操作完成后,Angular 并不是馬上接著執(zhí)行上面列表中的操作,而是會開始下一次 digest cycle,即 Angular 會把來自上一次 digest cycle 的值與當前值比較(譯者注:這個列表稱為列表 2):

檢查已經(jīng)傳給子組件用來更新其屬性的值,是否與當前將要傳入的值相同

檢查已經(jīng)傳給當前組件用來更新 DOM 值,是否與當前將要傳入的值相同

針對每一個子組件執(zhí)行相同的檢查(譯者注:就是如果子組件還有子組件,子組件會繼續(xù)執(zhí)行上面兩步的操作,依次遞歸下去。)

記住這個檢查只在開發(fā)環(huán)境下執(zhí)行,我會在后文解釋原因。

讓我們一起看一個簡單示例,假設(shè)你有一個父組件 A 和一個子組件 B,而 A 組件有 nametext 屬性,在 A 組件模板里使用 name 屬性的模板表達式:

template: "{{name}}"

同時,還有一個 B 子組件,并將 A 父組件的 text 屬性以輸入屬性綁定方式傳給 B 子組件:

@Component({
    selector: "a-comp",
    template: `
        {{name}}
        
    `
})
export class AComponent {
    name = "I am A component";
    text = "A message for the child component`;

那么當 Angular 執(zhí)行變更檢測的時候會發(fā)生什么呢?首先是從檢查父組件 A 開始,根據(jù)上面列表 1 列出的行為,第一步是更新所有子組件/指令的綁定屬性(binding property),所以 Angular 會計算 text 表達式的值為 A message for the child component,并將值向下傳給子組件 B,同時,Angular 還會在當前組件視圖中存儲這個值:

view.oldValues[0] = "A message for the child component";

第二步是執(zhí)行上面列表 1 列出的執(zhí)行幾個生命周期鉤子。(譯者注:即調(diào)用子組件 BngOnInitOnChangesngDoCheck 這三個生命周期鉤子。)

第三步是計算模板表達式 {{name}} 的值為 I am A component,然后更新當前組件 A 的 DOM,同時,Angular 還會在當前組件視圖中存儲這個值:

view.oldValues[1] = "I am A component";

第四步是為子組件 B 執(zhí)行以上第一步到第三步的相同操作,一旦 B 組件檢查完畢,那本次 digest loop 結(jié)束。(譯者注:我們知道 Angular 程序是由組件樹構(gòu)成的,當前父組件 A 組件做了第一二三步,完事后子組件 B 同樣會去做第一二三步,如果 B 組件還有子組件 C,同樣 C 也會做第一二三步,一直遞歸下去,直到當前樹枝的最末端,即最后一個組件沒有子組件為止。這一次過程稱為 digest loop。)

如果處于開發(fā)者模式,Angular 還會執(zhí)行上面列表 2 列出的 digest cycle 循環(huán)核查。現(xiàn)在假設(shè)當 A 組件已經(jīng)把 text 屬性值向下傳入給 B 組件并保存該值后,這時 text 值突變?yōu)?updated text,這樣在 Angular 運行 digest cycle 循環(huán)核查時,會執(zhí)行列表 2 中第一步操作,即檢查當前digest cycle 的 text 屬性值與上一次時的 text 屬性值是否發(fā)生變化:

AComponentView.instance.text === view.oldValues[0]; // false
"A message for the child component" === "updated text"; // false

結(jié)果是發(fā)生變化,這時 Angular 會拋出 ExpressionChangedAfterItHasBeenCheckedError 錯誤。

列表 1 中第三步操作也同樣會執(zhí)行 digest cycle 循環(huán)檢查,如果 name 屬性已經(jīng)在 DOM 中被渲染,并且在組件視圖中已經(jīng)被存儲了,那這時 name 屬性值突變同樣會有同樣錯誤:

AComponentView.instance.name === view.oldValues[1]; // false
"I am A component" === "updated name"; // false

你可能會問上面提到的 textname 屬性值發(fā)生突變,這會發(fā)生么?讓我們一起往下看。

屬性值突變的原因

屬性值突變的罪魁禍首是子組件或指令,一起看一個簡單證明示例吧。我會先使用最簡單的例子,然后舉個更貼近現(xiàn)實的例子。你可能知道子組件或指令可以注入它們的父組件,假設(shè)子組件 B 注入它的父組件 A,然后更新綁定屬性 text。我們在子組件 BngOnInit 生命周期鉤子中更新父組件 A 的屬性,這是因為 ngOnInit 生命周期鉤子會在屬性綁定完成后觸發(fā)(譯者注:參考列表 1,第一二步操作):

export class BComponent {
    @Input() text;

    constructor(private parent: AppComponent) {}

    ngOnInit() {
        this.parent.text = "updated text";
    }
}

果然會報錯:

Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: "A message for the child component". Current value: "updated text".

現(xiàn)在我們再同樣改變父組件 Aname 屬性:

ngOnInit() {
    this.parent.name = "updated name";
}

納尼,居然沒有報錯!!!怎么可能?

如果你往上翻看列表 1 的操作執(zhí)行順序,你會發(fā)現(xiàn) ngOnInit 生命周期鉤子會在 DOM 更新操作執(zhí)行前觸發(fā),所以不會報錯。為了有報錯,看來我們需要換一個生命周期鉤子,ngAfterViewInit 是個不錯的選項:

export class BComponent {
    @Input() text;

    constructor(private parent: AppComponent) {}

    ngAfterViewInit() {
        this.parent.name = "updated name";
    }
}

還好,終于有報錯了:

AppComponent.ngfactory.js:8 ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: "I am A component". Current value: "updated name".

當然,真實世界的例子會更加復雜,改變父組件屬性從而引發(fā) DOM 渲染,通常間接是因為使用服務(wù)(services)或可觀察者(observables)引發(fā)的,不過根本原因還是一樣的。

現(xiàn)在讓我們看看真實世界的案例吧。

共享服務(wù)(Shared service)

這個模式案例可查看代碼 plunker。這個程序設(shè)計為父子組件有個共享的服務(wù),子組件修改了共享服務(wù)的某個屬性值,響應(yīng)式地導致父組件的屬性值發(fā)生改變。我把它稱為非直接父組件屬性更新,因為不像上面的示例,它明顯不是子組件立刻改變父組件屬性值。

同步事件廣播

這個模式案例可查看代碼 plunker。這個程序設(shè)計為子組件拋出一個事件,而父組件監(jiān)聽這個事件,而這個事件會引起父組件屬性值發(fā)生改變。同時這些屬性值又被父組件作為輸入屬性綁定傳給子組件。這也是非直接父組件屬性更新。

動態(tài)組件實例化

這個模式有點不同于前面兩個影響的是輸入屬性綁定,它引起的是 DOM 更新從而拋出錯誤,可查看代碼 plunker。這個程序設(shè)計為父組件在 ngAfterViewInit 生命周期鉤子動態(tài)添加子組件。因為添加子組件會觸發(fā) DOM 修改,并且 ngAfterViewInit 生命周期鉤子也是在 DOM 更新后觸發(fā)的,所以同樣會拋出錯誤。

解決方案

如果你仔細查看錯誤描述的最后部分:

Expression has changed after it was checked. Previous value:… Has it been created?in a change detection hook??

根據(jù)上面描述,通常的解決方案是使用正確的生命周期鉤子來創(chuàng)建動態(tài)組件。例如上面創(chuàng)建動態(tài)組件的示例,其解決方案就是把組件創(chuàng)建代碼移到 ngOnInit 生命周期鉤子里。盡管官方文檔說 ViewChild 只有在 ngAfterViewInit 鉤子后才有效,但是當創(chuàng)建視圖時它就已經(jīng)填入了子組件,所以在早期階段就可用。(譯者注:Angular 官網(wǎng)說的是 View queries are set before the?ngAfterViewInit?callback is called,就已經(jīng)說明了 ViewChild 是在 ngAfterViewInit 鉤子前生效,不明白作者為啥要說之后才能生效。)

如果你 google 下就知道解決這個錯誤一般有兩種方式:異步更新屬性和手動強迫變更檢測。盡管我列出這兩個解決方案,但不建議這么去做,我將會解釋原因。

異步更新

這里需要注意的事情是變更檢測和核查循環(huán)(verification digests)都是同步的,這意味著如果我們在核查循環(huán)(verification loop)運行時去異步更新屬性值,會導致錯誤,測試下吧:

export class BComponent {
    name = "I am B component";
    @Input() text;

    constructor(private parent: AppComponent) {}

    ngOnInit() {
        setTimeout(() => {
            this.parent.text = "updated text";
        });
    }

    ngAfterViewInit() {
        setTimeout(() => {
            this.parent.name = "updated name";
        });
    }
}

實際上沒有拋出錯誤(譯者注:耍我呢!),這是因為 setTimeout() 函數(shù)會讓回調(diào)在下一個 VM turn 中作為宏觀任務(wù)(macrotask)被執(zhí)行。如果使用 Promise.then 回調(diào)來包裝,也可能在當前 VM turn 中執(zhí)行完同步代碼后,緊接著在當前 VM turn 繼續(xù)執(zhí)行回調(diào):(譯者注:VM turn 就是 Virtual Machine Turn,等于 browser task,這涉及到 JS 引擎如何執(zhí)行 JS 代碼的知識,這又是一塊大知識,不詳述,有興趣可以參考這篇經(jīng)典文章 Tasks, microtasks, queues and schedules ,或者這篇詳細描述的文檔 從瀏覽器多進程到JS單線程,JS運行機制最全面的一次梳理 。)

Promise.resolve(null).then(() => this.parent.name = "updated name");

與宏觀任務(wù)(macrotask)不同,Promise.then 會把回調(diào)構(gòu)造成微觀任務(wù)(microtask),微觀任務(wù)會在當前同步代碼執(zhí)行完后再緊接著被執(zhí)行,所以在核查之后會緊接著更新屬性值。想要更多學習 Angular 的宏觀任務(wù)和圍觀任務(wù),可以查看我寫的 ?I reverse-engineered Zones (zone.js) and here is what I’ve found

如果你使用 EventEmitter 你可以傳入 true 參數(shù)實現(xiàn)異步:

new EventEmitter(true);
強迫式變更檢測

另一種解決方案是在第一次變更檢測和核查循環(huán)階段之間,再一次迫使 Angular 執(zhí)行父組件 A 的變更檢測(譯者注:由于 Angular 先是變更檢測,然后核查循環(huán),所以這段意思是變更檢測完后,再去變更檢測)。最佳時期是在 ngAfterViewInit 鉤子里去觸發(fā)父組件 A 的變更檢測,因為這個父組件的鉤子函數(shù)會在所有子組件已經(jīng)執(zhí)行完它們自己的變更檢測后被觸發(fā),而恰恰是子組件做它們自己的變更檢測時可能會改變父組件屬性值:

export class AppComponent {
    name = "I am A component";
    text = "A message for the child component";

    constructor(private cd: ChangeDetectorRef) {
    }

    ngAfterViewInit() {
        this.cd.detectChanges();
    }

很好,沒有報錯,不過這個解決方案仍然有個問題。如果我們?yōu)楦附M件 A 觸發(fā)變更檢測,Angular 仍然會觸發(fā)它的所有子組件變更檢測,這可能重新會導致父組件屬性值發(fā)生改變。

為何需要循環(huán)核查(verification loop)

Angular 實行的是從上到下的單向數(shù)據(jù)流,當父組件改變值已經(jīng)被同步后(譯者注:即父組件模型和視圖已經(jīng)同步后),不允許子組件去更新父組件的屬性,這樣確保在第一次 digest loop 后,整個組件樹是穩(wěn)定的。如果屬性值發(fā)生改變,那么依賴于這些屬性的消費者(譯者注:即子組件)就需要同步,這會導致組件樹不穩(wěn)定。在我們的示例中,子組件 B 依賴于父組件的 text 屬性,每當 text 屬性改變時,除非它已經(jīng)被傳給 B 組件,否則整個組件樹是不穩(wěn)定的。對于父組件 A 中的 DOM 模板也同樣道理,它是 A 模型中屬性的消費者,并在 UI 中渲染出這些數(shù)據(jù),如果這些屬性沒有被及時同步,那么用戶將會在頁面上看到錯誤的數(shù)據(jù)信息。

數(shù)據(jù)同步過程是在變更檢測期間發(fā)生的,特別是列表 1 中的操作。所以如果當同步操作執(zhí)行完畢后,在子組件中去更新父組件屬性時,會發(fā)生什么呢?你將會得到不穩(wěn)定的組件樹,這樣的狀態(tài)是不可測的,大多數(shù)時候你將會給用戶展現(xiàn)錯誤的信息,并且很難調(diào)試。

那為何不等到組件樹穩(wěn)定了再去執(zhí)行變更檢測呢?答案很簡答,因為它可能永遠不會穩(wěn)定。如果把子組件更新了父組件的屬性,作為該屬性改變時的響應(yīng),那將會無限循環(huán)下去。當然,正如我之前說的,不管是直接更新還是依賴的情況,這都不是重點,但是在現(xiàn)實世界中,更新還是依賴一般都是非直接的。

有趣的是,AngularJS 并沒有單向數(shù)據(jù)流,所以它會試圖想辦法去讓組件樹穩(wěn)定。但是它會經(jīng)常導致那個著名的錯誤 10 $digest() iterations reached. Aborting!,去谷歌這個錯誤,你會驚訝發(fā)現(xiàn)關(guān)于這個錯誤的問題有很多。

最后一個問題你可能會問為什么只有在開發(fā)模式下會執(zhí)行 digest cycle 呢?我猜可能因為相比于一個運行錯誤,不穩(wěn)定的模型并不是個大問題,畢竟它可能在下一次循環(huán)檢查數(shù)據(jù)同步后變得穩(wěn)定。然而,最好能在開發(fā)階段注意可能發(fā)生的錯誤,總比在生產(chǎn)環(huán)境去調(diào)試錯誤要好得多。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/93695.html

相關(guān)文章

  • [] 你真的知道 Angular 單向數(shù)據(jù)流嗎

    摘要:所以,單向數(shù)據(jù)流的意思是指在變更檢測期間屬性綁定變更的架構(gòu)。相反,輸出綁定過程并沒有在變更檢測期間內(nèi)運行,所以它沒有把單向數(shù)據(jù)流轉(zhuǎn)變?yōu)殡p向數(shù)據(jù)流。說的單向數(shù)據(jù)流說的是服務(wù)層,而不是視圖層嗷。 原文鏈接: Do you really know what unidirectional data flow means in?Angular 關(guān)于單向數(shù)據(jù)流,還可以參考這篇文章,且文中還有 y...

    fox_soyoung 評論0 收藏0
  • [] $digest 在 Angular 中重生

    摘要:但如果一個組件在生命周期鉤子里改變父組件屬性,卻是可以的,因為這個鉤子函數(shù)是在更新父組件屬性變化之前調(diào)用的注即第步,在第步之前調(diào)用。 原文鏈接:Angular.js’ $digest is reborn in the newer version of Angular showImg(https://segmentfault.com/img/remote/146000001468785...

    incredible 評論0 收藏0
  • 這5篇文章將使你成為一個Angular Change Detection專家。

    摘要:編寫工作首先介紹了一個稱為的內(nèi)部組件表示,并解釋了變更檢測過程在視圖上運行。本文主要由兩部分組成第一部分探討錯誤產(chǎn)生的原因,第二部分提出可能的修正。它對我意義重大,它能幫助其他人看到這篇文章。 在過去的8個月里,我大部分空閑時間都是reverse-engineering Angular。我最感興趣的話題是變化檢測。我認為它是框架中最重要的部分,因為它負責像DOM更新、輸入綁定和查詢列表...

    Coly 評論0 收藏0
  • 正在失業(yè)中的《課多周刊》(第3期)

    摘要:正在失業(yè)中的課多周刊第期我們的微信公眾號,更多精彩內(nèi)容皆在微信公眾號,歡迎關(guān)注。若有幫助,請把課多周刊推薦給你的朋友,你的支持是我們最大的動力。是一種禍害譯本文淺談了在中關(guān)于的不好之處。淺談超時一運維的排查方式。 正在失業(yè)中的《課多周刊》(第3期) 我們的微信公眾號:fed-talk,更多精彩內(nèi)容皆在微信公眾號,歡迎關(guān)注。 若有幫助,請把 課多周刊 推薦給你的朋友,你的支持是我們最大的...

    robin 評論0 收藏0
  • 正在失業(yè)中的《課多周刊》(第3期)

    摘要:正在失業(yè)中的課多周刊第期我們的微信公眾號,更多精彩內(nèi)容皆在微信公眾號,歡迎關(guān)注。若有幫助,請把課多周刊推薦給你的朋友,你的支持是我們最大的動力。是一種禍害譯本文淺談了在中關(guān)于的不好之處。淺談超時一運維的排查方式。 正在失業(yè)中的《課多周刊》(第3期) 我們的微信公眾號:fed-talk,更多精彩內(nèi)容皆在微信公眾號,歡迎關(guān)注。 若有幫助,請把 課多周刊 推薦給你的朋友,你的支持是我們最大的...

    Joyven 評論0 收藏0

發(fā)表評論

0條評論

andong777

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<