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

資訊專欄INFORMATION COLUMN

[譯] Angular 屬性綁定更新機制

tianhang / 3343人閱讀

摘要:本文主要介紹輸入輸出綁定方式,特別是當父組件輸入綁定值變化時,如何更新子組件輸入值。更新指令的屬性上文中已經描述了函數是用來更新元素的屬性,而是用來更新子組件的輸入綁定屬性,并且變更檢測期間傳入的參數就是函數。

原文鏈接:The mechanics of property bindings update in Angular

所有現代前端框架都是用組件來合成 UI,這樣很自然就會產生父子組件層級,這就需要框架提供父子組件通信的機制。同樣,Angular 也提供了兩種方式來實現父子組件通信:輸入輸出綁定共享服務。對于 stateless presentational components 我更喜歡輸入輸出綁定方式,然而對于 stateful container components 我使用共享服務方式。

本文主要介紹輸入輸出綁定方式,特別是當父組件輸入綁定值變化時,Angular 如何更新子組件輸入值。如果想了解 Angular 如何更新當前組件 DOM,可以查看 譯 Angular DOM 更新機制,這篇文章也會有助于加深對本文的理解。由于我們將探索 Angular 如何更新 DOM 元素和組件的輸入綁定屬性,所以假定你知道 Angular 內部是如何表現組件和指令的,如果你不是很了解并且很感興趣,可以查看 譯 為何 Angular 內部沒有發現組件, 這篇文章主要講了 Angular 內部如何使用指令形式來表示組件。而本文對于組件和指令兩個概念互換使用,因為 Angular 內部就是把組件當做指令。

模板綁定語法

你可能知道 Angular 提供了 屬性綁定語法 —— [],這個語法很通用,它可以用在子組件上,也可以用在原生 DOM 元素上。如果你想從父組件把數據傳給子組件 b-comp 或者原生 DOM 元素 span,你可以在父組件模板中這么寫:

import { Component } from "@angular/core";

@Component({
  moduleId: module.id,
  selector: "a-comp",
  template: `
      
      
  `
})
export class AComponent {
  AText = "some";
}

你不必為原生 DOM 元素做些額外的工作,但是對于子組件 b-comp 你需要申明輸入屬性 textContent

@Component({
    selector: "b-comp",
    template: "Comes from parent: {{textContent}}"
})
export class BComponent {
    @Input() textContent;
}

這樣當父組件 AComponent.AText 屬性改變時,Angular 會自動更新子組件 BComponent.textContent 屬性,和原生元素 span.textContent 屬性。同時,還會調用子組件 BComponent 的生命周期鉤子函數 ngOnChanges(注:實際上還有 ngDoCheck,見下文)。

你可能好奇 Angular 是怎么知道 BComponentspan 支持 textContent 綁定的。這是因為 Angular 編譯器在解析模板時,如果遇到簡單 DOM 元素如 span,就去查找這個元素是否定義在 dom_element_schema_registry,從而知道它是 HTMLElement 子類,textContent 是其中的一個屬性(注:可以試試如果 span 綁定一個 [abc]=AText 就報錯,沒法識別 abc 屬性);如果遇到了組件或指令,就去查看其裝飾器 @Component/@Directive 的元數據 input 屬性里是否有該綁定屬性項,如果沒有,編譯器同樣會拋出錯誤:

Can’t bind to ‘textContent’ since it isn’t a known property of?…

這些知識都很好理解,現在讓我們進一步看看其內部發生了什么。

組件工廠

盡管在子組件 BComponentspan 元素綁定了輸入屬性,但是輸入綁定更新所需要的信息全部在父組件 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 更新機制譯 為何 Angular 內部沒有發現組件,就會對上面代碼中的各個視圖節點比較熟悉了。前兩個節點中,jit_elementDef_2 是元素節點,jit_directiveDef_5 是指令節點,這兩個組成了子組件 BComponent;第三個節點 jit_elementDef_2 也是元素節點,組成了 span 元素。

節點綁定

相同類型的節點使用相同的節點定義函數,但區別是接收的參數不同,比如 jit_directiveDef_5 節點定義函數參數如下:

jit_directiveDef_5(..., jit_BComponent6, [], {
    textContent: [0, "textContent"]
}, ...),

其中,參數 {textContent: [0, "textContent"]} 叫做 props,這點可以查看 directiveDef 函數的參數列表:

directiveDef(..., props?: {[name: string]: [number, string]}, ...)

props 參數是一個對象,每一個鍵為綁定屬性名,對應的值為綁定索引和綁定屬性名組成的數組,比如本例中只有一個綁定,textContent 對應的值為:

{textContent: [0, "textContent"]}

如果指令有多個綁定,比如:

props 參數值也包含兩個屬性:

jit_directiveDef5(49152, null, 0, jit_BComponent6, [], {
    textContent: [0, "textContent"],
    otherProp: [1, "otherProp"]
}, null),

Angular 會使用這些值來生成當前指令節點的 binding,從而生成當前視圖的指令節點。在變更檢測時,每一個 binding 決定 Angular 使用哪種操作來更新節點和提供上下文信息,綁定類型是通過 BindingFlags 設置的(注:每一個綁定定義是 BindingDef,它的屬性 flags: BindingFlags 決定 Angular 該采取什么操作,比如 Class 型綁定和 Style 型綁定都會調用對應的操作函數,見下文)。比如,如果是屬性綁定,編譯器會設置綁定標志位為:

export const enum BindingFlags {
    TypeProperty = 1 << 3,
注:上文說完了指令定義函數的參數,下面說說元素定義函數的參數。

本例中,因為 span 元素有屬性綁定,編譯器會設置綁定參數為 [[8, "textContent", 0]]

jit_elementDef2(..., "span", [], [[8, "textContent", 0]], ...)

不同于指令節點,對元素節點來說,綁定參數結構是個二維數組,因為 span 元素只有一個綁定,所以它僅僅只有一個子數組。數組 [8, "textContent", 0] 中第一個參數也同樣是綁定標志位 BindingFlags,決定 Angular 應該采取什么類型操作(注:[8, "textContent", 0] 中的 8 表示為 property 型綁定):

export const enum BindingFlags {
    TypeProperty = 1 << 3, // 8

其他類型標志位已經在文章 譯 Angular DOM 更新機制 有所解釋:

TypeElementAttribute = 1 << 0,
TypeElementClass = 1 << 1,
TypeElementStyle = 1 << 2,

編譯器不會為指令定義提供綁定標志位,因為指令的綁定類型也只能是 BindingFlags.TypeProperty

注:節點綁定 這一節主要講的是對于元素節點來說,每一個節點的 binding 類型是由 BindingFlags 決定的;對于指令節點來說,每一個節點的 binding 類型只能是 BindingFlags.TypeProperty
updateRenderer 和 updateDirectives

組件工廠代碼里,編譯器還為我們生成了兩個函數:

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 更新機制,應該對第二個函數即 updateRenderer 有所熟悉。第一個函數叫做 updateDirectives。這兩個函數都是 ViewUpdateFn 類型接口,兩者都是視圖定義的屬性:

interface ViewDefinition {
  flags: ViewFlags;
  updateDirectives: ViewUpdateFn;
  updateRenderer: ViewUpdateFn;

有趣的是這兩個函數的函數體基本相同,參數都是 _ck_v,并且兩個函數的對應參數都指向同一個對象,所以為何需要兩個函數?

因為在變更檢測期間,這是不同階段的兩個不同行為:

更新子組件的輸入綁定屬性

更新當前組件的 DOM 元素

這兩個操作是在變更檢測的不同階段執行,所以 Angular 需要兩個獨立的函數分別在對應的階段調用:

updateDirectives——變更檢測的開始階段被調用,來更新子組件的輸入綁定屬性

updateRenderer——變更檢測的中間階段被調用,來更新當前組件的 DOM 元素

這兩個函數都會在 Angular 每次的變更檢測時 被調用,并且函數參數也是在這時被傳入的。讓我們看看函數內部做了哪些工作。

_ck 就是 check 的縮寫,其實就是函數 prodCheckAndUpdateNode,另一個參數就是 組件視圖數據。函數的主要功能就是從組件對象里拿到綁定屬性的當前值,然后和視圖數據對象、視圖節點索引等一起傳入 prodCheckAndUpdateNode 函數。其中,因為 Angular 會更新每一個視圖的 DOM,所以需要傳入當前視圖的索引。如果我們有兩個 span 和兩個組件:




編譯器生成的 updateRenderer 函數和 updateDirectives 函數如下:

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

沒有什么更復雜的東西,這兩個函數還不是重點,重點是 _ck 函數,接著往下看。

更新元素的屬性

從上文我們知道,編譯器生成的 updateRenderer 函數會在每一次變更檢測被調用,用來更新 DOM 元素的屬性,并且其參數 _ck 就是函數 prodCheckAndUpdateNode。對于 DOM 元素的更新,該函數經過一系列的函數調用后,最終會調用函數 checkAndUpdateElementValue,這個函數會檢查綁定標志位是 [attr.name, class.name, style.some] 其中的哪一個,又或者是屬性綁定(注:可查看源碼這段 L233-L250):

case BindingFlags.TypeElementAttribute -> setElementAttribute
case BindingFlags.TypeElementClass     -> setElementClass
case BindingFlags.TypeElementStyle     -> setElementStyle
case BindingFlags.TypeProperty         -> setElementProperty;

上面代碼就是剛剛說的幾個綁定類型,當綁定標志位是 BindingFlags.TypeProperty,會調用函數 setElementProperty,該函數內部也是通過調用 DOM Renderer 的 setProperty 方法來更新 DOM。

注:setElementProperty 函數里這行代碼 view.renderer.setProperty(renderNode,name, renderValue);,renderer 就是 Renderer2 interface,它僅僅是一個接口,在瀏覽器平臺下,它的實現就是 DefaultDomRenderer2
更新指令的屬性

上文中已經描述了 updateRenderer 函數是用來更新元素的屬性,而 updateDirective 是用來更新子組件的輸入綁定屬性,并且變更檢測期間傳入的參數 _ck 就是函數 prodCheckAndUpdateNode。只是進過一系列函數調用后,最終調用的函數卻是checkAndUpdateDirectiveInline,這是因為這次節點的標志位是 NodeFlags.TypeDirective(注:可查看源碼 L428-L429),checkAndUpdateDirectiveInline 函數主要功能如下:

從當前視圖節點里獲取組件/指令對象(注:查看 L156

檢查組件/指令對象的綁定屬性值是否發生改變(注:查看 L160-L199

如果屬性發生改變:

a. 如果變更策略設置為 OnPush,設置視圖狀態為 checksEnabled(注:查看 L438

b. 更新子組件的綁定屬性值(注:查看 L446

c. 準備 SimpleChange 數據和更新視圖的 oldValues 屬性,新值替換舊值(注:查看 L451-L454

d. 調用生命周期鉤子 ngOnChanges(注:查看 L201

如果該視圖是首次執行變更檢測,則調用生命周期鉤子 ngOnInit(注:查看 L205

調用生命周期鉤子 ngDoCheck(注:查看 L233

當然,只有在生命周期鉤子在組件內定義了才被調用,Angular 使用 NodeDef 節點標志位來判斷是否有生命周期鉤子,如果查看源碼你會發現類似如下代碼(注:查看 L203-L207):

if (... && (def.flags & NodeFlags.OnInit)) {
  directive.ngOnInit();
}
if (def.flags & NodeFlags.DoCheck) {
  directive.ngDoCheck();
}

和更新元素節點一樣,更新指令時也同樣把上一次的值存儲在視圖數據的屬性 oldValues 里(注:即上面的 3.c 步驟)。

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/107751.html

相關文章

  • [] 你真的知道 Angular 單向數據流嗎

    摘要:所以,單向數據流的意思是指在變更檢測期間屬性綁定變更的架構。相反,輸出綁定過程并沒有在變更檢測期間內運行,所以它沒有把單向數據流轉變為雙向數據流。說的單向數據流說的是服務層,而不是視圖層嗷。 原文鏈接: Do you really know what unidirectional data flow means in?Angular 關于單向數據流,還可以參考這篇文章,且文中還有 y...

    fox_soyoung 評論0 收藏0
  • [] Angular DOM 更新機制

    摘要:注更新元素節點和文本節點都提到了渲染器,這也是一個重要的概念。每一個視圖對象都有一個屬性,即是的引用,也就是組件渲染器,的實際更新操作由它完成。 原文鏈接:The mechanics of DOM updates in Angular showImg(https://segmentfault.com/img/remote/1460000014687960?w=419&h=268); ...

    xumenger 評論0 收藏0
  • [] 關于 `ExpressionChangedAfterItHasBeenCheckedErro

    摘要:本文將解釋引起這個錯誤的內在原因,檢測機制的內部原理,提供導致這個錯誤的共同行為,并給出修復這個錯誤的解決方案。這一次過程稱為。這個程序設計為子組件拋出一個事件,而父組件監聽這個事件,而這個事件會引起父組件屬性值發生改變。 原文鏈接:Everything you need to know about the ExpressionChangedAfterItHasBeenCheckedE...

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

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

    incredible 評論0 收藏0
  • [] 如何對 Angular Controller 進行單元測試

    摘要:原文地址上面一篇文章簡單介紹了如何使用進行的單元測試我們用了一段簡單的代碼進行計算的測試。添加測試接下來終于到了我們的主題,添加一些單元測試給我們忽略代碼中部分,主要集中在的代碼中。 原文地址:http://www.bradoncode.com/blog/2015/05/17/angularjs-testing-controller/@Bradley Braithwaite show...

    aboutU 評論0 收藏0

發表評論

0條評論

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