成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

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

andong777 / 3050人閱讀

摘要:本文將解釋引起這個(gè)錯(cuò)誤的內(nèi)在原因,檢測(cè)機(jī)制的內(nèi)部原理,提供導(dǎo)致這個(gè)錯(cuò)誤的共同行為,并給出修復(fù)這個(gè)錯(cuò)誤的解決方案。這一次過(guò)程稱(chēng)為。這個(gè)程序設(shè)計(jì)為子組件拋出一個(gè)事件,而父組件監(jiān)聽(tīng)這個(gè)事件,而這個(gè)事件會(huì)引起父組件屬性值發(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 拋出的一個(gè)錯(cuò)誤:ExpressionChangedAfterItHasBeenCheckedError,通常提出這個(gè)問(wèn)題的 Angular 開(kāi)發(fā)者都不理解變更檢測(cè)(change detection)的原理,不理解為何產(chǎn)生這個(gè)錯(cuò)誤的數(shù)據(jù)更新檢查是必須的,甚至很多開(kāi)發(fā)者認(rèn)為這是 Angular 框架的一個(gè) bug(譯者注:Angular 提供變更檢測(cè)功能,包括自動(dòng)觸發(fā)和手動(dòng)觸發(fā),自動(dòng)觸發(fā)是默認(rèn)的,手動(dòng)觸發(fā)是在使用 ChangeDetectionStrategy.OnPush 關(guān)閉自動(dòng)觸發(fā)的情況下生效。如何手動(dòng)觸發(fā),參考 Triggering change detection manually in Angular)。當(dāng)然不是了!其實(shí)這是 Angular 的警告機(jī)制,防止由于模型數(shù)據(jù)(model data)與視圖 UI 不一致,導(dǎo)致頁(yè)面上存在錯(cuò)誤或過(guò)時(shí)的數(shù)據(jù)展示給用戶(hù)。

本文將解釋引起這個(gè)錯(cuò)誤的內(nèi)在原因,檢測(cè)機(jī)制的內(nèi)部原理,提供導(dǎo)致這個(gè)錯(cuò)誤的共同行為,并給出修復(fù)這個(gè)錯(cuò)誤的解決方案。最后章節(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)變更檢測(cè)行為

一個(gè)運(yùn)行的 Angular 程序其實(shí)是一個(gè)組件樹(shù),在變更檢測(cè)期間,Angular 會(huì)按照以下順序檢查每一個(gè)組件(譯者注:這個(gè)列表稱(chēng)為列表 1):

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

調(diào)用所有子組件/指令的三個(gè)生命周期鉤子:ngOnInit,OnChanges,ngDoCheck

更新當(dāng)前組件的 DOM

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

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

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

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

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

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

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

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

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

template: "{{name}}"

同時(shí),還有一個(gè) 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`;

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

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

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

第三步是計(jì)算模板表達(dá)式 {{name}} 的值為 I am A component,然后更新當(dāng)前組件 A 的 DOM,同時(shí),Angular 還會(huì)在當(dāng)前組件視圖中存儲(chǔ)這個(gè)值:

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

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

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

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

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

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

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

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

屬性值突變的原因

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

export class BComponent {
    @Input() text;

    constructor(private parent: AppComponent) {}

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

果然會(huì)報(bào)錯(cuò):

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

現(xiàn)在我們?cè)偻瑯痈淖兏附M件 Aname 屬性:

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

納尼,居然沒(méi)有報(bào)錯(cuò)?。?!怎么可能?

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

export class BComponent {
    @Input() text;

    constructor(private parent: AppComponent) {}

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

還好,終于有報(bào)錯(cuò)了:

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

當(dāng)然,真實(shí)世界的例子會(huì)更加復(fù)雜,改變父組件屬性從而引發(fā) DOM 渲染,通常間接是因?yàn)槭褂梅?wù)(services)或可觀察者(observables)引發(fā)的,不過(guò)根本原因還是一樣的。

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

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

這個(gè)模式案例可查看代碼 plunker。這個(gè)程序設(shè)計(jì)為父子組件有個(gè)共享的服務(wù),子組件修改了共享服務(wù)的某個(gè)屬性值,響應(yīng)式地導(dǎo)致父組件的屬性值發(fā)生改變。我把它稱(chēng)為非直接父組件屬性更新,因?yàn)椴幌裆厦娴氖纠?,它明顯不是子組件立刻改變父組件屬性值。

同步事件廣播

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

動(dòng)態(tài)組件實(shí)例化

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

解決方案

如果你仔細(xì)查看錯(cuò)誤描述的最后部分:

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

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

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

異步更新

這里需要注意的事情是變更檢測(cè)和核查循環(huán)(verification digests)都是同步的,這意味著如果我們?cè)诤瞬檠h(huán)(verification loop)運(yùn)行時(shí)去異步更新屬性值,會(huì)導(dǎo)致錯(cuò)誤,測(cè)試下吧:

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";
        });
    }
}

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

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

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

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

new EventEmitter(true);
強(qiáng)迫式變更檢測(cè)

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

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

    constructor(private cd: ChangeDetectorRef) {
    }

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

很好,沒(méi)有報(bào)錯(cuò),不過(guò)這個(gè)解決方案仍然有個(gè)問(wèn)題。如果我們?yōu)楦附M件 A 觸發(fā)變更檢測(cè),Angular 仍然會(huì)觸發(fā)它的所有子組件變更檢測(cè),這可能重新會(huì)導(dǎo)致父組件屬性值發(fā)生改變。

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

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

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

那為何不等到組件樹(shù)穩(wěn)定了再去執(zhí)行變更檢測(cè)呢?答案很簡(jiǎn)答,因?yàn)樗赡苡肋h(yuǎn)不會(huì)穩(wěn)定。如果把子組件更新了父組件的屬性,作為該屬性改變時(shí)的響應(yīng),那將會(huì)無(wú)限循環(huán)下去。當(dāng)然,正如我之前說(shuō)的,不管是直接更新還是依賴(lài)的情況,這都不是重點(diǎn),但是在現(xiàn)實(shí)世界中,更新還是依賴(lài)一般都是非直接的。

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

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

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

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/93695.html

相關(guān)文章

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

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

    fox_soyoung 評(píng)論0 收藏0
  • [] $digest 在 Angular 中重生

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

    incredible 評(píng)論0 收藏0
  • 這5篇文章將使你成為一個(gè)Angular Change Detection專(zhuān)家。

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

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

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

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

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

    Joyven 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

andong777

|高級(jí)講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<