[譯] Angular 屬性綁定更新機制 - Laravel/Angular 技術分享
本篇文章主要的講述了關於angularjs屬性綁定更新機制,還有angularjs更新屬性的詳解,都在這篇文章中,現在就讓我們一起來看這篇文章吧
angularjs屬性綁定更新機制解釋:
所有現代前端框架都是用元件來合成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: ` <b-comp [textContent]="AText"></b-comp> <span [textContent]="AText"></span> ` }) 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 是怎麼知道 BComponent
和 span
支援 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 …
這些知識都很好理解,現在讓我們進一步看看其內部發生了什麼。
元件工廠
儘管在子元件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 更新機制 或譯為何Angular 內部沒有發現元件,就會對上面程式碼中的各個視圖節點比較熟悉了。在前兩個節點中,jit_elementDef_2
是元素節點,jit_directiveDef_5
是指令節點,這兩個組成了子元件BComponent
;第三個節點 jit_elementDef_2
也是元素節點,組成了span
元素。 (想看更多就到PHP中文網AngularJS開發手冊中學習)
節點綁定
#相同類型的節點使用相同的節點定義函數,但區別是接收的參數不同,例如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']}
如果指令有多個綁定定,比如:
<b-comp [textContent]="AText" [otherProp]="AProp">
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
和两个组件:
<b-comp [textContent]="AText"></b-comp> <b-comp [textContent]="AText"></b-comp> <span [textContent]="AText"></span> <span [textContent]="AText"></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]
其中的哪一个,又或者是属性绑定:
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
checkAndUpdateDirectiveInline 函数主要功能如下:
从当前视图节点里获取组件/指令对象
检查组件/指令对象的绑定属性值是否发生改变
-
如果属性发生改变:
a. 如果变更策略设置为
OnPush
,设置视图状态为checksEnabled
b. 更新子组件的绑定属性值
c. 准备
SimpleChange
数据和更新视图的oldValues
属性,新值替换旧值d. 调用生命周期钩子 ngOnChanges
如果该视图是首次执行变更检测,则调用生命周期钩子 ngOnInit
调用生命周期钩子 ngDoCheck
当然,只有在生命周期钩子在组件内定义了才被调用,Angular 使用 NodeDef 节点标志位来判断是否有生命周期钩子,如果查看源码你会发现类似如下代码:
if (... && (def.flags & NodeFlags.OnInit)) { directive.ngOnInit(); } if (def.flags & NodeFlags.DoCheck) { directive.ngDoCheck(); }
和更新元素节点一样,更新指令时也同样把上一次的值存储在视图数据的属性 oldValues 里(注:即上面的 3.c
步骤)。
好了,本篇文章到这就结束了(想看更多就到PHP中文网AngularJS使用手册中学习),有问题的可以在下方留言提问。
以上是[譯] Angular 屬性綁定更新機制 - Laravel/Angular 技術分享的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

熱門話題

如何使用WebSocket和JavaScript實現線上語音辨識系統引言:隨著科技的不斷發展,語音辨識技術已成為了人工智慧領域的重要組成部分。而基於WebSocket和JavaScript實現的線上語音辨識系統,具備了低延遲、即時性和跨平台的特點,成為了廣泛應用的解決方案。本文將介紹如何使用WebSocket和JavaScript來實現線上語音辨識系

WebSocket與JavaScript:實現即時監控系統的關鍵技術引言:隨著互聯網技術的快速發展,即時監控系統在各個領域中得到了廣泛的應用。而實現即時監控的關鍵技術之一就是WebSocket與JavaScript的結合使用。本文將介紹WebSocket與JavaScript在即時監控系統中的應用,並給出程式碼範例,詳細解釋其實作原理。一、WebSocket技

如何利用JavaScript和WebSocket實現即時線上點餐系統介紹:隨著網路的普及和技術的進步,越來越多的餐廳開始提供線上點餐服務。為了實現即時線上點餐系統,我們可以利用JavaScript和WebSocket技術。 WebSocket是一種基於TCP協定的全雙工通訊協議,可實現客戶端與伺服器的即時雙向通訊。在即時線上點餐系統中,當使用者選擇菜餚並下訂單

如何使用WebSocket和JavaScript實現線上預約系統在當今數位化的時代,越來越多的業務和服務都需要提供線上預約功能。而實現一個高效、即時的線上預約系統是至關重要的。本文將介紹如何使用WebSocket和JavaScript來實作一個線上預約系統,並提供具體的程式碼範例。一、什麼是WebSocketWebSocket是一種在單一TCP連線上進行全雙工

JavaScript和WebSocket:打造高效的即時天氣預報系統引言:如今,天氣預報的準確性對於日常生活以及決策制定具有重要意義。隨著技術的發展,我們可以透過即時獲取天氣數據來提供更準確可靠的天氣預報。在本文中,我們將學習如何使用JavaScript和WebSocket技術,來建立一個高效的即時天氣預報系統。本文將透過具體的程式碼範例來展示實現的過程。 We

JavaScript教學:如何取得HTTP狀態碼,需要具體程式碼範例前言:在Web開發中,經常會涉及到與伺服器進行資料互動的場景。在與伺服器進行通訊時,我們經常需要取得傳回的HTTP狀態碼來判斷操作是否成功,並根據不同的狀態碼來進行對應的處理。本篇文章將教你如何使用JavaScript來取得HTTP狀態碼,並提供一些實用的程式碼範例。使用XMLHttpRequest

JavaScript中的HTTP狀態碼取得方法簡介:在進行前端開發中,我們常常需要處理與後端介面的交互,而HTTP狀態碼就是其中非常重要的一部分。了解並取得HTTP狀態碼有助於我們更好地處理介面傳回的資料。本文將介紹使用JavaScript取得HTTP狀態碼的方法,並提供具體程式碼範例。一、什麼是HTTP狀態碼HTTP狀態碼是指當瀏覽器向伺服器發起請求時,服務

JavaScript是一種廣泛應用於Web開發的程式語言,而WebSocket則是一種用於即時通訊的網路協定。結合二者的強大功能,我們可以打造一個高效率的即時影像處理系統。本文將介紹如何利用JavaScript和WebSocket來實作這個系統,並提供具體的程式碼範例。首先,我們需要明確指出即時影像處理系統的需求和目標。假設我們有一個攝影機設備,可以擷取即時的影像數
