摘要:本質(zhì)上,本文主要解釋內(nèi)部是如何定義組件和指令的,并引入新的視圖節(jié)點(diǎn)定義指令定義。大多數(shù)指令使用屬性選擇器,但是有一些也選擇元素選擇器。實(shí)際上,表單指令就是使用元素選擇器來(lái)把特定行為附著在元素上。但是由于編譯器會(huì)為每一個(gè)
原文鏈接:Here is why you will not find components inside Angular
Component is just a directive with a template? Or is it?
從我開(kāi)始使用 Angular 開(kāi)始,就被組件和指令間區(qū)別的問(wèn)題所困惑,尤其對(duì)那些從 Angular.js 世界來(lái)的人,因?yàn)?Angular.js 里只有指令,盡管我們也經(jīng)常把它當(dāng)做組件來(lái)使用。如果你在網(wǎng)上搜這個(gè)問(wèn)題解釋,很多都會(huì)這么解釋(注:為清晰理解,不翻譯):
Components are just directives with a content defined in a template…Angular components are a subset of directives. Unlike directives, components always have…
Components are high-order directives with templates and serve as…
這些說(shuō)法貌似都對(duì),我在查看由 Angular 編譯器編譯組件生成的視圖工廠源碼里,的確沒(méi)發(fā)現(xiàn)組件定義,你如果查看也只會(huì)發(fā)現(xiàn) 指令。
注:使用 Angular-CLI ng new 一個(gè)新項(xiàng)目,執(zhí)行 ng serve 運(yùn)行程序后,就可在 Chrome Dev Tools 的 Source Tab 的 ng:// 域下查看到編譯組件后生成的 **.ngfactory.js 文件,該文件代碼即上面說(shuō)的視圖工廠源碼。
但是我在網(wǎng)上沒(méi)有找到 原因解釋,因?yàn)橄胍涝蚓捅仨殞?duì) Angular 內(nèi)部工作原理比較熟悉,如果上面的問(wèn)題也困讓了你很長(zhǎng)一段時(shí)間,那本文正適合你。讓我們一起探索其中的奧秘并做好準(zhǔn)備吧。
本質(zhì)上,本文主要解釋 Angular 內(nèi)部是如何定義組件和指令的,并引入新的視圖節(jié)點(diǎn)定義——指令定義。
注:視圖節(jié)點(diǎn)還包括元素節(jié)點(diǎn)和文本節(jié)點(diǎn),有興趣可查看 譯 Angular DOM 更新機(jī)制 。視圖
如果你讀過(guò)我之前寫(xiě)的文章,尤其是 譯 Angular DOM 更新機(jī)制,你可能會(huì)明白 Angular 程序內(nèi)部是一棵視圖樹(shù),每一個(gè)視圖都是由視圖工廠生成的,并且每個(gè)視圖包含具有特定功能的不同視圖節(jié)點(diǎn)。在剛剛提到的文章中(那篇文章對(duì)了解本文很重要嗷),我介紹過(guò)兩個(gè)最簡(jiǎn)單的節(jié)點(diǎn)類型——元素節(jié)點(diǎn)定義和文本節(jié)點(diǎn)定義。元素節(jié)點(diǎn)定義是用來(lái)創(chuàng)建所有 DOM 元素節(jié)點(diǎn),而文本節(jié)點(diǎn)定義是用來(lái)創(chuàng)建所有 DOM 文本節(jié)點(diǎn) 。
所以如果你寫(xiě)了如下的一個(gè)模板:
Hello {{name}}
Angular Compiler 將會(huì)編譯這個(gè)模板,并生成兩個(gè)元素節(jié)點(diǎn),即 div 和 h1 DOM 元素,和一個(gè)文本節(jié)點(diǎn),即 Hello {{name}} DOM 文本。這些都是很重要的節(jié)點(diǎn),因?yàn)闆](méi)有它們,你在屏幕上看不到任何東西。但是組件合成模式告訴我們可以嵌套組件,所以必然另一種視圖節(jié)點(diǎn)來(lái)嵌入組件。為了搞清楚這些特殊節(jié)點(diǎn)是什么,首先需要了解組件是由什么組成的。本質(zhì)上,組件本質(zhì)上是具有特定行為的 DOM 元素,而這些行為是在組件類里實(shí)現(xiàn)的。首先看下 DOM 元素吧。
自定義 DOM 元素你可能知道在 html 里可以創(chuàng)建一個(gè)新的 HTML 標(biāo)簽,比如,如果不使用框架,你可以直接在 html 里插入一個(gè)新的標(biāo)簽:
然后查詢這個(gè) DOM 節(jié)點(diǎn)并檢查類型,你會(huì)發(fā)現(xiàn)它是個(gè)完全合法的 DOM 元素(注:你可以在一個(gè) html 文件里試試這部分代碼,甚至可以寫(xiě)上
const element = document.querySelector("a-comp"); element.nodeType === Node.ELEMENT_NODE; // true
瀏覽器會(huì)使用 HTMLUnknownElement 接口來(lái)創(chuàng)建 a-comp 元素,這個(gè)接口又繼承 HTMLElement 接口,但是它不需要實(shí)現(xiàn)任何屬性或方法。你可以使用 CSS 來(lái)裝飾它,也可以給它添加事件監(jiān)聽(tīng)器來(lái)監(jiān)聽(tīng)一些普遍事件,比如 click 事件。所以正如我說(shuō)的,a-comp 是一個(gè)完全合法的 DOM 元素。
然后,你可以把它轉(zhuǎn)變成 自定義 DOM 元素 來(lái)增強(qiáng)這個(gè)元素,你需要為它多帶帶創(chuàng)建一個(gè)類并使用 JS API 來(lái)注冊(cè)這個(gè)類:
class AComponent extends HTMLElement {...} window.customElements.define("a-comp", AComponent);
這是不是和你一直在做的事情有些類似呀。
沒(méi)錯(cuò),這和你在 Angular 中定義一個(gè)組件非常類似,實(shí)際上,Angular 框架嚴(yán)格遵循 Web 組件標(biāo)準(zhǔn)但是為我們簡(jiǎn)化了很多事情,所以我們不必自己創(chuàng)建 shadow root 并掛載到宿主元素(注:關(guān)于 shadow root 的概念網(wǎng)上資料很多,其實(shí)在 Chrome Dev Tools 里,點(diǎn)擊右上角 settings,然后點(diǎn)擊 Preferences -> Elements,打開(kāi) Show user agent shadow root 后,這樣你就可以在 Elements 面板里看到很多 DOM 元素下的 shadow root)。然而,我們?cè)?Angular 中創(chuàng)建的組件并沒(méi)有注冊(cè)為自定義元素,它會(huì)被 Angular 以特定方式去處理。如果你對(duì)沒(méi)有框架時(shí)如何創(chuàng)建組件很好奇,你可以查看 Custom Elements v1: Reusable Web Components 。
現(xiàn)在已經(jīng)知道,我們可以創(chuàng)建任何一個(gè) HTML 標(biāo)簽并在模板里使用它。所以,如果我們?cè)?Angular 的組件模板里使用這個(gè)標(biāo)簽,框架將會(huì)給這個(gè)標(biāo)簽創(chuàng)建元素定義(注:這是由 Angular Compiler 編譯生成的):
function View_AppComponent_0(_l) { return jit_viewDef2(0, [ jit_elementDef3(0, null, null, 1, "a-comp", [], ...) ]) }
然而,你得需要在 module 或組件裝飾器屬性里添加 schemas: [CUSTOM_ELEMENTS_SCHEMA],來(lái)告訴 Angular 你在使用自定義元素,否則 Angular Compiler 會(huì)拋出錯(cuò)誤(注:所以如果需要使用某個(gè)組件,你不得不在 module.declarations 或 module.entryComponents 或 component.entryComponents 去注冊(cè)這個(gè)組件):
"a-comp" is not a known element: 1. If "c-comp" is an Angular component, then ... 2. If "c-comp" is a Web Component then add...
所以,我們已經(jīng)有了 DOM 元素但是還沒(méi)有附著在元素上的類呢,那 Angular 里除了組件外還有其他特殊類沒(méi)?當(dāng)然有——指令。讓我們看看指令有些啥。
指令定義你可能知道每一個(gè)指令都有一個(gè)選擇器,用來(lái)掛載到特定的 DOM 元素上。大多數(shù)指令使用屬性選擇器(attribute selectors),但是有一些也選擇元素選擇器(element selectors)。實(shí)際上,Angular 表單指令就是使用 元素選擇器 form 來(lái)把特定行為附著在 html form元素上。
所以,讓我們創(chuàng)建一個(gè)空指令類,并把它附著在自定義元素上,再看看視圖定義是什么樣的:
@Directive({selector: "a-comp"}) export class ADirective {}
然后核查下生成的視圖工廠:
function View_AppComponent_0(_l) { return jit_viewDef2(0, [ jit_elementDef3(0, null, null, 1, "a-comp", [], ...), jit_directiveDef4(16384, null, 0, jit_ADirective5, [],...) ], null, null); }
現(xiàn)在 Angular Compiler 在視圖定義函數(shù)的第二個(gè)參數(shù)數(shù)組里,添加了新生成的指令定義 jit_directiveDef4 節(jié)點(diǎn),并放在元素定義節(jié)點(diǎn) jit_elementDef3 后面。同時(shí)設(shè)置元素定義的 childCount 為 1,因?yàn)楦街谠厣系乃兄噶疃紩?huì)被看做該元素的子元素。
指令定義是個(gè)很簡(jiǎn)單的節(jié)點(diǎn)定義,它是由 directiveDef 函數(shù)生成的,該函數(shù)參數(shù)列表如下(注:現(xiàn)在 Angular v5.x 版本略有不同):
Name | Description |
---|---|
matchedQueries | used when querying child nodes |
childCount | specifies how many children the current element have |
ctor | reference to the component or directive constructor |
deps | an array of constructor dependencies |
props | an array of input property bindings |
outputs | an array of output property bindings |
本文我們只對(duì) ctor 參數(shù)感興趣,它僅僅是我們定義的 ADirective 類的引用。當(dāng) Angular 創(chuàng)建指令對(duì)象時(shí),它會(huì)實(shí)例化一個(gè)指令類,并存儲(chǔ)在視圖節(jié)點(diǎn)的 provider data 屬性里。
所以我們看到組件其實(shí)僅僅是一個(gè)元素定義加上一個(gè)指令定義,但僅僅如此么?你可能知道 Angular 總是沒(méi)那么簡(jiǎn)單啊!
組件展示從上文知道,我們可以通過(guò)創(chuàng)建一個(gè)自定義元素和附著在該元素上的指令,來(lái)模擬創(chuàng)建出一個(gè)組件。讓我們定義一個(gè)真實(shí)的組件,并把由該組件編譯生成的視圖工廠類,與我們上面實(shí)驗(yàn)性的視圖工廠類做個(gè)比較:
@Component({
selector: "a-comp",
template: "I am A component"
})
export class AComponent {}
做好準(zhǔn)備了么?下面是生成的視圖工廠類:
function View_AppComponent_0() { return jit_viewDef2(0, [ jit_elementDef3(0, null, null, 1, "a-comp", [], ... jit_View_AComponent_04, jit__object_Object_5), jit_directiveDef6(49152, null, 0, jit_AComponent7, [], ...)
好的,現(xiàn)在我們僅僅驗(yàn)證了上文所說(shuō)的。本示例中, Angular 使用兩種視圖節(jié)點(diǎn)來(lái)表示組件——元素節(jié)點(diǎn)定義和指令節(jié)點(diǎn)定義。但是當(dāng)使用一個(gè)真實(shí)的組件時(shí),就會(huì)發(fā)現(xiàn)這兩個(gè)節(jié)點(diǎn)定義的參數(shù)列表還是有些不同的。讓我們看看有哪些不同吧。
節(jié)點(diǎn)類型節(jié)點(diǎn)類型(NodeFlags)是所有節(jié)點(diǎn)定義函數(shù)的第一個(gè)參數(shù)(注:最新 Angular v5.* 中參數(shù)列表有點(diǎn)點(diǎn)不一樣,如 directiveDef 中第二個(gè)參數(shù)才是 NodeFlags)。它實(shí)際上是 NodeFlags 位掩碼(注:查看源碼,是用二進(jìn)制表示的),包含一系列特定的節(jié)點(diǎn)信息,大部分在 變更檢測(cè)循環(huán) 時(shí)被框架使用。并且不同節(jié)點(diǎn)類型采用不同數(shù)字:16384 表示簡(jiǎn)單指令節(jié)點(diǎn)類型(注:僅僅是指令,可看 TypeDirective);49152 表示組件指令節(jié)點(diǎn)類型(注:組件加指令,即 TypeDirective + Component)。為了更好理解這些標(biāo)志位是如何被編譯器設(shè)置的,讓我們先轉(zhuǎn)換為二進(jìn)制:
16384 = 100000000000000 // 15th bit set 49152 = 1100000000000000 // 15th and 16th bit set
如果你很好奇這些轉(zhuǎn)換是怎么做的,可以查看我寫(xiě)的文章 The simple math behind decimal-binary conversion algorithms 。所以,對(duì)于簡(jiǎn)單指令 Angular 編譯器會(huì)設(shè)置 15-th 位為 1:
TypeDirective = 1 << 14
而對(duì)于組件節(jié)點(diǎn)會(huì)設(shè)置 15-th 和 16-th 位為 1:
TypeDirective = 1 << 14 Component = 1 << 15
現(xiàn)在明白為何這些數(shù)字不同了。對(duì)于指令來(lái)說(shuō),生成的節(jié)點(diǎn)被標(biāo)記為 TypeDirective 節(jié)點(diǎn);對(duì)于組件指令來(lái)說(shuō),生成的節(jié)點(diǎn)除了被標(biāo)記為 TypeDirective 節(jié)點(diǎn),還被標(biāo)記為 Component 節(jié)點(diǎn)。
視圖定義解析器因?yàn)?a-comp 是一個(gè)組件,所以對(duì)于下面的簡(jiǎn)單模板:
I am A component
編譯器會(huì)編譯它,生成一個(gè)帶有視圖定義和視圖節(jié)點(diǎn)的工廠函數(shù):
function View_AComponent_0(_l) { return jit_viewDef1(0, [ jit_elementDef2(0, null, null, 1, "span", [], ...), jit_textDef3(null, ["I am A component"])
Angular 是一個(gè)視圖樹(shù),所以父視圖需要有個(gè)對(duì)子視圖的引用,子視圖會(huì)被存儲(chǔ)在元素節(jié)點(diǎn)內(nèi)。本例中,a-comp 的視圖存儲(chǔ)在為
注:這段由于涉及大量的源碼函數(shù),會(huì)比較晦澀。作者講的是創(chuàng)建視圖的具體過(guò)程,細(xì)致到很多函數(shù)的調(diào)用??傊恍枰涀∫稽c(diǎn)就行:視圖解析器通過(guò)解析視圖工廠(ViewDefinitionFactory)得到視圖(ViewDefinition)。細(xì)節(jié)暫不用管。組件渲染器類型拿到了視圖,又該如何畫(huà)出來(lái)呢?看下文。
Angular 根據(jù)組件裝飾器中定義的 ViewEncapsulation 模式來(lái)決定使用哪種 DOM 渲染器:
Emulated Encapsulation Renderer
Shadow Renderer
Default Renderer
以上組件渲染器是通過(guò) DomRendererFactory2 來(lái)創(chuàng)建的。componentRendererType 參數(shù)是在元素定義里被傳入的,本例即是 jit__object_Object_5(注:上面代碼里有這個(gè)對(duì)象,是 jit_elementDef3() 的最后一個(gè)參數(shù)),該參數(shù)是渲染器的一個(gè)基本描述符,用來(lái)決定使用哪一個(gè)渲染器渲染組件。其中,最重要的是視圖封裝模式和所用于組件的樣式(注:componentRendererType 參數(shù)的結(jié)構(gòu)是 RendererType2):
{ styles:[["h1[_ngcontent-%COMP%] {color: green}"]], encapsulation:0 }
如果你為組件定義了樣式,編譯器會(huì)自動(dòng)設(shè)置組件的封裝模式為 ViewEncapsulation.Emulated,或者你可以在組件裝飾器里顯式設(shè)置 encapsulation 屬性。如果沒(méi)有設(shè)置任何樣式,并且也沒(méi)有顯式設(shè)置 encapsulation 屬性,那描述符會(huì)被設(shè)置為 ViewEncapsulation.Emulated,并被 忽略生效,使用這種描述符的組件會(huì)使用父組件的組件渲染器。
子指令現(xiàn)在,最后一個(gè)問(wèn)題是,如果我們像下面這樣,把一個(gè)指令作用在組件模板上,會(huì)生成什么:
我們已經(jīng)知道當(dāng)為 AComponent 生成工廠函數(shù)時(shí),編譯器會(huì)為 a-comp 元素創(chuàng)建元素定義,會(huì)為 AComponent 類創(chuàng)建指令定義。但是由于編譯器會(huì)為每一個(gè)指令生成指令定義節(jié)點(diǎn),所以上面模板的工廠函數(shù)像這樣(注:Angular v5.* 版本是會(huì)為
function View_AppComponent_0() { return jit_viewDef2(0, [ jit_elementDef3(0, null, null, 2, "a-comp", [], ... jit_View_AComponent_04, jit__object_Object_5), jit_directiveDef6(49152, null, 0, jit_AComponent7, [], ...) jit_directiveDef6(16384, null, 0, jit_ADirective8, [], ...)
上面代碼都是我們熟悉的,僅僅是多添加了一個(gè)指令定義,和子組件數(shù)量增加為 2。
以上就是全部了!
注:全文主要講的是組件(視圖)在 Angular 內(nèi)部是如何用指令節(jié)點(diǎn)和元素節(jié)點(diǎn)定義的。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/107752.html
摘要:本文主要介紹輸入輸出綁定方式,特別是當(dāng)父組件輸入綁定值變化時(shí),如何更新子組件輸入值。更新指令的屬性上文中已經(jīng)描述了函數(shù)是用來(lái)更新元素的屬性,而是用來(lái)更新子組件的輸入綁定屬性,并且變更檢測(cè)期間傳入的參數(shù)就是函數(shù)。 原文鏈接:The mechanics of property bindings update in Angular showImg(https://segmentfault....
摘要:但如果一個(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...
摘要:本文將解釋引起這個(gè)錯(cuò)誤的內(nèi)在原因,檢測(cè)機(jī)制的內(nèi)部原理,提供導(dǎo)致這個(gè)錯(cuò)誤的共同行為,并給出修復(fù)這個(gè)錯(cuò)誤的解決方案。這一次過(guò)程稱為。這個(gè)程序設(shè)計(jì)為子組件拋出一個(gè)事件,而父組件監(jiān)聽(tīng)這個(gè)事件,而這個(gè)事件會(huì)引起父組件屬性值發(fā)生改變。 原文鏈接:Everything you need to know about the ExpressionChangedAfterItHasBeenCheckedE...
摘要:大多數(shù)初學(xué)者會(huì)認(rèn)為也有封裝規(guī)則,但實(shí)際上沒(méi)有。第二個(gè)規(guī)則是最后導(dǎo)入模塊的,會(huì)覆蓋前面導(dǎo)入模塊的。 原文鏈接:Avoiding common confusions with modules in Angular showImg(https://segmentfault.com/img/remote/1460000015298243?w=270&h=360); Angular Modul...
摘要:所以,單向數(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...
閱讀 1919·2021-11-22 15:24
閱讀 1362·2021-11-12 10:36
閱讀 3282·2021-09-28 09:36
閱讀 1918·2021-09-02 15:15
閱讀 2819·2019-08-30 15:54
閱讀 2441·2019-08-30 11:02
閱讀 2458·2019-08-29 13:52
閱讀 3598·2019-08-26 11:53