摘要:本文主要介紹輸入輸出綁定方式,特別是當(dāng)父組件輸入綁定值變化時(shí),如何更新子組件輸入值。更新指令的屬性上文中已經(jīng)描述了函數(shù)是用來(lái)更新元素的屬性,而是用來(lái)更新子組件的輸入綁定屬性,并且變更檢測(cè)期間傳入的參數(shù)就是函數(shù)。
原文鏈接:The mechanics of property bindings update in Angular
所有現(xiàn)代前端框架都是用組件來(lái)合成 UI,這樣很自然就會(huì)產(chǎn)生父子組件層級(jí),這就需要框架提供父子組件通信的機(jī)制。同樣,Angular 也提供了兩種方式來(lái)實(shí)現(xiàn)父子組件通信:輸入輸出綁定和共享服務(wù)。對(duì)于 stateless presentational components 我更喜歡輸入輸出綁定方式,然而對(duì)于 stateful container components 我使用共享服務(wù)方式。
本文主要介紹輸入輸出綁定方式,特別是當(dāng)父組件輸入綁定值變化時(shí),Angular 如何更新子組件輸入值。如果想了解 Angular 如何更新當(dāng)前組件 DOM,可以查看 譯 Angular DOM 更新機(jī)制,這篇文章也會(huì)有助于加深對(duì)本文的理解。由于我們將探索 Angular 如何更新 DOM 元素和組件的輸入綁定屬性,所以假定你知道 Angular 內(nèi)部是如何表現(xiàn)組件和指令的,如果你不是很了解并且很感興趣,可以查看 譯 為何 Angular 內(nèi)部沒有發(fā)現(xiàn)組件, 這篇文章主要講了 Angular 內(nèi)部如何使用指令形式來(lái)表示組件。而本文對(duì)于組件和指令兩個(gè)概念互換使用,因?yàn)?Angular 內(nèi)部就是把組件當(dāng)做指令。
模板綁定語(yǔ)法你可能知道 Angular 提供了 屬性綁定語(yǔ)法 —— [],這個(gè)語(yǔ)法很通用,它可以用在子組件上,也可以用在原生 DOM 元素上。如果你想從父組件把數(shù)據(jù)傳給子組件 b-comp 或者原生 DOM 元素 span,你可以在父組件模板中這么寫:
import { Component } from "@angular/core"; @Component({ moduleId: module.id, selector: "a-comp", template: `` }) export class AComponent { AText = "some"; }
你不必為原生 DOM 元素做些額外的工作,但是對(duì)于子組件 b-comp 你需要申明輸入屬性 textContent:
@Component({ selector: "b-comp", template: "Comes from parent: {{textContent}}" }) export class BComponent { @Input() textContent; }
這樣當(dāng)父組件 AComponent.AText 屬性改變時(shí),Angular 會(huì)自動(dòng)更新子組件 BComponent.textContent 屬性,和原生元素 span.textContent 屬性。同時(shí),還會(huì)調(diào)用子組件 BComponent 的生命周期鉤子函數(shù) ngOnChanges(注:實(shí)際上還有 ngDoCheck,見下文)。
你可能好奇 Angular 是怎么知道 BComponent 和 span 支持 textContent 綁定的。這是因?yàn)?Angular 編譯器在解析模板時(shí),如果遇到簡(jiǎn)單 DOM 元素如 span,就去查找這個(gè)元素是否定義在 dom_element_schema_registry,從而知道它是 HTMLElement 子類,textContent 是其中的一個(gè)屬性(注:可以試試如果 span 綁定一個(gè) [abc]=AText 就報(bào)錯(cuò),沒法識(shí)別 abc 屬性);如果遇到了組件或指令,就去查看其裝飾器 @Component/@Directive 的元數(shù)據(jù) input 屬性里是否有該綁定屬性項(xiàng),如果沒有,編譯器同樣會(huì)拋出錯(cuò)誤:
Can’t bind to ‘textContent’ since it isn’t a known property of?…
這些知識(shí)都很好理解,現(xiàn)在讓我們進(jìn)一步看看其內(nèi)部發(fā)生了什么。
組件工廠盡管在子組件 BComponent 和 span 元素綁定了輸入屬性,但是輸入綁定更新所需要的信息全部在父組件 AComponent 的組件工廠里。讓我們看下 AComponent 的組件工廠代碼:
function View_AComponent_0(_l) { return jit_viewDef1(0, [ jit_elementDef_2(..., "b-comp", ...), jit_directiveDef_5(..., jit_BComponent6, [], { textContent: [0, "textContent"] }, ...), jit_elementDef_2(..., "span", [], [[8, "textContent", 0]], ...) ], function (_ck, _v) { var _co = _v.component; var currVal_0 = _co.AText; var currVal_1 = "d"; _ck(_v, 1, 0, currVal_0, currVal_1); }, function (_ck, _v) { var _co = _v.component; var currVal_2 = _co.AText; _ck(_v, 2, 0, currVal_2); }); }
如果你讀了 譯 Angular DOM 更新機(jī)制 或 譯 為何 Angular 內(nèi)部沒有發(fā)現(xiàn)組件,就會(huì)對(duì)上面代碼中的各個(gè)視圖節(jié)點(diǎn)比較熟悉了。前兩個(gè)節(jié)點(diǎn)中,jit_elementDef_2 是元素節(jié)點(diǎn),jit_directiveDef_5 是指令節(jié)點(diǎn),這兩個(gè)組成了子組件 BComponent;第三個(gè)節(jié)點(diǎn) jit_elementDef_2 也是元素節(jié)點(diǎn),組成了 span 元素。
節(jié)點(diǎn)綁定相同類型的節(jié)點(diǎn)使用相同的節(jié)點(diǎn)定義函數(shù),但區(qū)別是接收的參數(shù)不同,比如 jit_directiveDef_5 節(jié)點(diǎn)定義函數(shù)參數(shù)如下:
jit_directiveDef_5(..., jit_BComponent6, [], { textContent: [0, "textContent"] }, ...),
其中,參數(shù) {textContent: [0, "textContent"]} 叫做 props,這點(diǎn)可以查看 directiveDef 函數(shù)的參數(shù)列表:
directiveDef(..., props?: {[name: string]: [number, string]}, ...)
props 參數(shù)是一個(gè)對(duì)象,每一個(gè)鍵為綁定屬性名,對(duì)應(yīng)的值為綁定索引和綁定屬性名組成的數(shù)組,比如本例中只有一個(gè)綁定,textContent 對(duì)應(yīng)的值為:
{textContent: [0, "textContent"]}
如果指令有多個(gè)綁定,比如:
props 參數(shù)值也包含兩個(gè)屬性:
jit_directiveDef5(49152, null, 0, jit_BComponent6, [], { textContent: [0, "textContent"], otherProp: [1, "otherProp"] }, null),
Angular 會(huì)使用這些值來(lái)生成當(dāng)前指令節(jié)點(diǎn)的 binding,從而生成當(dāng)前視圖的指令節(jié)點(diǎn)。在變更檢測(cè)時(shí),每一個(gè) binding 決定 Angular 使用哪種操作來(lái)更新節(jié)點(diǎn)和提供上下文信息,綁定類型是通過 BindingFlags 設(shè)置的(注:每一個(gè)綁定定義是 BindingDef,它的屬性 flags: BindingFlags 決定 Angular 該采取什么操作,比如 Class 型綁定和 Style 型綁定都會(huì)調(diào)用對(duì)應(yīng)的操作函數(shù),見下文)。比如,如果是屬性綁定,編譯器會(huì)設(shè)置綁定標(biāo)志位為:
export const enum BindingFlags { TypeProperty = 1 << 3,
注:上文說(shuō)完了指令定義函數(shù)的參數(shù),下面說(shuō)說(shuō)元素定義函數(shù)的參數(shù)。
本例中,因?yàn)?span 元素有屬性綁定,編譯器會(huì)設(shè)置綁定參數(shù)為 [[8, "textContent", 0]]:
jit_elementDef2(..., "span", [], [[8, "textContent", 0]], ...)
不同于指令節(jié)點(diǎn),對(duì)元素節(jié)點(diǎn)來(lái)說(shuō),綁定參數(shù)結(jié)構(gòu)是個(gè)二維數(shù)組,因?yàn)?span 元素只有一個(gè)綁定,所以它僅僅只有一個(gè)子數(shù)組。數(shù)組 [8, "textContent", 0] 中第一個(gè)參數(shù)也同樣是綁定標(biāo)志位 BindingFlags,決定 Angular 應(yīng)該采取什么類型操作(注:[8, "textContent", 0] 中的 8 表示為 property 型綁定):
export const enum BindingFlags { TypeProperty = 1 << 3, // 8
其他類型標(biāo)志位已經(jīng)在文章 譯 Angular DOM 更新機(jī)制 有所解釋:
TypeElementAttribute = 1 << 0, TypeElementClass = 1 << 1, TypeElementStyle = 1 << 2,
編譯器不會(huì)為指令定義提供綁定標(biāo)志位,因?yàn)橹噶畹慕壎愋鸵仓荒苁?BindingFlags.TypeProperty。
注:節(jié)點(diǎn)綁定 這一節(jié)主要講的是對(duì)于元素節(jié)點(diǎn)來(lái)說(shuō),每一個(gè)節(jié)點(diǎn)的 binding 類型是由 BindingFlags 決定的;對(duì)于指令節(jié)點(diǎn)來(lái)說(shuō),每一個(gè)節(jié)點(diǎn)的 binding 類型只能是 BindingFlags.TypeProperty。updateRenderer 和 updateDirectives
組件工廠代碼里,編譯器還為我們生成了兩個(gè)函數(shù):
function (_ck, _v) { var _co = _v.component; var currVal_0 = _co.AText; var currVal_1 = _co.AProp; _ck(_v, 1, 0, currVal_0, currVal_1); }, function (_ck, _v) { var _co = _v.component; var currVal_2 = _co.AText; _ck(_v, 2, 0, currVal_2); }
如果你讀了 譯 Angular DOM 更新機(jī)制,應(yīng)該對(duì)第二個(gè)函數(shù)即 updateRenderer 有所熟悉。第一個(gè)函數(shù)叫做 updateDirectives。這兩個(gè)函數(shù)都是 ViewUpdateFn 類型接口,兩者都是視圖定義的屬性:
interface ViewDefinition { flags: ViewFlags; updateDirectives: ViewUpdateFn; updateRenderer: ViewUpdateFn;
有趣的是這兩個(gè)函數(shù)的函數(shù)體基本相同,參數(shù)都是 _ck 和 _v,并且兩個(gè)函數(shù)的對(duì)應(yīng)參數(shù)都指向同一個(gè)對(duì)象,所以為何需要兩個(gè)函數(shù)?
因?yàn)樵谧兏鼨z測(cè)期間,這是不同階段的兩個(gè)不同行為:
更新子組件的輸入綁定屬性
更新當(dāng)前組件的 DOM 元素
這兩個(gè)操作是在變更檢測(cè)的不同階段執(zhí)行,所以 Angular 需要兩個(gè)獨(dú)立的函數(shù)分別在對(duì)應(yīng)的階段調(diào)用:
updateDirectives——變更檢測(cè)的開始階段被調(diào)用,來(lái)更新子組件的輸入綁定屬性
updateRenderer——變更檢測(cè)的中間階段被調(diào)用,來(lái)更新當(dāng)前組件的 DOM 元素
這兩個(gè)函數(shù)都會(huì)在 Angular 每次的變更檢測(cè)時(shí) 被調(diào)用,并且函數(shù)參數(shù)也是在這時(shí)被傳入的。讓我們看看函數(shù)內(nèi)部做了哪些工作。
_ck 就是 check 的縮寫,其實(shí)就是函數(shù) prodCheckAndUpdateNode,另一個(gè)參數(shù)就是 組件視圖數(shù)據(jù)。函數(shù)的主要功能就是從組件對(duì)象里拿到綁定屬性的當(dāng)前值,然后和視圖數(shù)據(jù)對(duì)象、視圖節(jié)點(diǎn)索引等一起傳入 prodCheckAndUpdateNode 函數(shù)。其中,因?yàn)?Angular 會(huì)更新每一個(gè)視圖的 DOM,所以需要傳入當(dāng)前視圖的索引。如果我們有兩個(gè) span 和兩個(gè)組件:
編譯器生成的 updateRenderer 函數(shù)和 updateDirectives 函數(shù)如下:
function(_ck, _v) { var _co = _v.component; var currVal_0 = _co.AText; // update first component _ck(_v, 1, 0, currVal_0); var currVal_1 = _co.AText; // update second component _ck(_v, 3, 0, currVal_1); }, function(_ck, _v) { var _co = _v.component; var currVal_2 = _co.AText; // update first span _ck(_v, 4, 0, currVal_2); var currVal_3 = _co.AText; // update second span _ck(_v, 5, 0, currVal_3); }
沒有什么更復(fù)雜的東西,這兩個(gè)函數(shù)還不是重點(diǎn),重點(diǎn)是 _ck 函數(shù),接著往下看。
更新元素的屬性從上文我們知道,編譯器生成的 updateRenderer 函數(shù)會(huì)在每一次變更檢測(cè)被調(diào)用,用來(lái)更新 DOM 元素的屬性,并且其參數(shù) _ck 就是函數(shù) prodCheckAndUpdateNode。對(duì)于 DOM 元素的更新,該函數(shù)經(jīng)過一系列的函數(shù)調(diào)用后,最終會(huì)調(diào)用函數(shù) checkAndUpdateElementValue,這個(gè)函數(shù)會(huì)檢查綁定標(biāo)志位是 [attr.name, class.name, style.some] 其中的哪一個(gè),又或者是屬性綁定(注:可查看源碼這段 L233-L250):
case BindingFlags.TypeElementAttribute -> setElementAttribute case BindingFlags.TypeElementClass -> setElementClass case BindingFlags.TypeElementStyle -> setElementStyle case BindingFlags.TypeProperty -> setElementProperty;
上面代碼就是剛剛說(shuō)的幾個(gè)綁定類型,當(dāng)綁定標(biāo)志位是 BindingFlags.TypeProperty,會(huì)調(diào)用函數(shù) setElementProperty,該函數(shù)內(nèi)部也是通過調(diào)用 DOM Renderer 的 setProperty 方法來(lái)更新 DOM。
注:setElementProperty 函數(shù)里這行代碼 view.renderer.setProperty(renderNode,name, renderValue);,renderer 就是 Renderer2 interface,它僅僅是一個(gè)接口,在瀏覽器平臺(tái)下,它的實(shí)現(xiàn)就是 DefaultDomRenderer2。更新指令的屬性
上文中已經(jīng)描述了 updateRenderer 函數(shù)是用來(lái)更新元素的屬性,而 updateDirective 是用來(lái)更新子組件的輸入綁定屬性,并且變更檢測(cè)期間傳入的參數(shù) _ck 就是函數(shù) prodCheckAndUpdateNode。只是進(jìn)過一系列函數(shù)調(diào)用后,最終調(diào)用的函數(shù)卻是checkAndUpdateDirectiveInline,這是因?yàn)檫@次節(jié)點(diǎn)的標(biāo)志位是 NodeFlags.TypeDirective(注:可查看源碼 L428-L429),checkAndUpdateDirectiveInline 函數(shù)主要功能如下:
從當(dāng)前視圖節(jié)點(diǎn)里獲取組件/指令對(duì)象(注:查看 L156)
檢查組件/指令對(duì)象的綁定屬性值是否發(fā)生改變(注:查看 L160-L199)
如果屬性發(fā)生改變:
a. 如果變更策略設(shè)置為 OnPush,設(shè)置視圖狀態(tài)為 checksEnabled(注:查看 L438)
b. 更新子組件的綁定屬性值(注:查看 L446)
c. 準(zhǔn)備 SimpleChange 數(shù)據(jù)和更新視圖的 oldValues 屬性,新值替換舊值(注:查看 L451-L454)
d. 調(diào)用生命周期鉤子 ngOnChanges(注:查看 L201)
如果該視圖是首次執(zhí)行變更檢測(cè),則調(diào)用生命周期鉤子 ngOnInit(注:查看 L205)
調(diào)用生命周期鉤子 ngDoCheck(注:查看 L233)
當(dāng)然,只有在生命周期鉤子在組件內(nèi)定義了才被調(diào)用,Angular 使用 NodeDef 節(jié)點(diǎn)標(biāo)志位來(lái)判斷是否有生命周期鉤子,如果查看源碼你會(huì)發(fā)現(xiàn)類似如下代碼(注:查看 L203-L207):
if (... && (def.flags & NodeFlags.OnInit)) { directive.ngOnInit(); } if (def.flags & NodeFlags.DoCheck) { directive.ngDoCheck(); }
和更新元素節(jié)點(diǎn)一樣,更新指令時(shí)也同樣把上一次的值存儲(chǔ)在視圖數(shù)據(jù)的屬性 oldValues 里(注:即上面的 3.c 步驟)。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/107751.html
摘要:所以,單向數(shù)據(jù)流的意思是指在變更檢測(cè)期間屬性綁定變更的架構(gòu)。相反,輸出綁定過程并沒有在變更檢測(cè)期間內(nèi)運(yùn)行,所以它沒有把單向數(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...
摘要:注更新元素節(jié)點(diǎn)和文本節(jié)點(diǎn)都提到了渲染器,這也是一個(gè)重要的概念。每一個(gè)視圖對(duì)象都有一個(gè)屬性,即是的引用,也就是組件渲染器,的實(shí)際更新操作由它完成。 原文鏈接:The mechanics of DOM updates in Angular showImg(https://segmentfault.com/img/remote/1460000014687960?w=419&h=268); ...
摘要:本文將解釋引起這個(gè)錯(cuò)誤的內(nèi)在原因,檢測(cè)機(jī)制的內(nèi)部原理,提供導(dǎo)致這個(gè)錯(cuò)誤的共同行為,并給出修復(fù)這個(gè)錯(cuò)誤的解決方案。這一次過程稱為。這個(gè)程序設(shè)計(jì)為子組件拋出一個(gè)事件,而父組件監(jiān)聽這個(gè)事件,而這個(gè)事件會(huì)引起父組件屬性值發(fā)生改變。 原文鏈接:Everything you need to know about the ExpressionChangedAfterItHasBeenCheckedE...
摘要:但如果一個(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...
摘要:原文地址上面一篇文章簡(jiǎn)單介紹了如何使用進(jìn)行的單元測(cè)試我們用了一段簡(jiǎn)單的代碼進(jìn)行計(jì)算的測(cè)試。添加測(cè)試接下來(lái)終于到了我們的主題,添加一些單元測(cè)試給我們忽略代碼中部分,主要集中在的代碼中。 原文地址:http://www.bradoncode.com/blog/2015/05/17/angularjs-testing-controller/@Bradley Braithwaite show...
閱讀 1458·2021-09-02 19:23
閱讀 1607·2021-08-11 11:19
閱讀 652·2019-08-30 15:55
閱讀 1663·2019-08-30 12:50
閱讀 2252·2019-08-30 11:23
閱讀 2191·2019-08-29 13:13
閱讀 1511·2019-08-28 18:13
閱讀 3350·2019-08-26 11:53