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

資訊專欄INFORMATION COLUMN

[譯] 為何 Angular 內部沒有發現組件

LiveVideoStack / 3544人閱讀

摘要:本質上,本文主要解釋內部是如何定義組件和指令的,并引入新的視圖節點定義指令定義。大多數指令使用屬性選擇器,但是有一些也選擇元素選擇器。實際上,表單指令就是使用元素選擇器來把特定行為附著在元素上。但是由于編譯器會為每一個

原文鏈接:Here is why you will not find components inside Angular

Component is just a directive with a template? Or is it?

從我開始使用 Angular 開始,就被組件和指令間區別的問題所困惑,尤其對那些從 Angular.js 世界來的人,因為 Angular.js 里只有指令,盡管我們也經常把它當做組件來使用。如果你在網上搜這個問題解釋,很多都會這么解釋(注:為清晰理解,不翻譯):

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…

這些說法貌似都對,我在查看由 Angular 編譯器編譯組件生成的視圖工廠源碼里,的確沒發現組件定義,你如果查看也只會發現 指令

注:使用 Angular-CLI ng new 一個新項目,執行 ng serve 運行程序后,就可在 Chrome Dev Tools 的 Source Tab 的 ng:// 域下查看到編譯組件后生成的 **.ngfactory.js 文件,該文件代碼即上面說的視圖工廠源碼。

但是我在網上沒有找到 原因解釋,因為想要知道原因就必須對 Angular 內部工作原理比較熟悉,如果上面的問題也困讓了你很長一段時間,那本文正適合你。讓我們一起探索其中的奧秘并做好準備吧。

本質上,本文主要解釋 Angular 內部是如何定義組件和指令的,并引入新的視圖節點定義——指令定義。

注:視圖節點還包括元素節點和文本節點,有興趣可查看 譯 Angular DOM 更新機制
視圖

如果你讀過我之前寫的文章,尤其是 譯 Angular DOM 更新機制,你可能會明白 Angular 程序內部是一棵視圖樹,每一個視圖都是由視圖工廠生成的,并且每個視圖包含具有特定功能的不同視圖節點。在剛剛提到的文章中(那篇文章對了解本文很重要嗷),我介紹過兩個最簡單的節點類型——元素節點定義和文本節點定義。元素節點定義是用來創建所有 DOM 元素節點,而文本節點定義是用來創建所有 DOM 文本節點

所以如果你寫了如下的一個模板:

Hello {{name}}

Angular Compiler 將會編譯這個模板,并生成兩個元素節點,即 divh1 DOM 元素,和一個文本節點,即 Hello {{name}} DOM 文本。這些都是很重要的節點,因為沒有它們,你在屏幕上看不到任何東西。但是組件合成模式告訴我們可以嵌套組件,所以必然另一種視圖節點來嵌入組件。為了搞清楚這些特殊節點是什么,首先需要了解組件是由什么組成的。本質上,組件本質上是具有特定行為的 DOM 元素,而這些行為是在組件類里實現的。首先看下 DOM 元素吧。

自定義 DOM 元素

你可能知道在 html 里可以創建一個新的 HTML 標簽,比如,如果不使用框架,你可以直接在 html 里插入一個新的標簽:

然后查詢這個 DOM 節點并檢查類型,你會發現它是個完全合法的 DOM 元素(注:你可以在一個 html 文件里試試這部分代碼,甚至可以寫上 A Component,結果是可以運行的,原因見下文):

const element = document.querySelector("a-comp");
element.nodeType === Node.ELEMENT_NODE; // true

瀏覽器會使用 HTMLUnknownElement 接口來創建 a-comp 元素,這個接口又繼承 HTMLElement 接口,但是它不需要實現任何屬性或方法。你可以使用 CSS 來裝飾它,也可以給它添加事件監聽器來監聽一些普遍事件,比如 click 事件。所以正如我說的,a-comp 是一個完全合法的 DOM 元素。

然后,你可以把它轉變成 自定義 DOM 元素 來增強這個元素,你需要為它多帶帶創建一個類并使用 JS API 來注冊這個類:

class AComponent extends HTMLElement {...}
window.customElements.define("a-comp", AComponent);

這是不是和你一直在做的事情有些類似呀。

沒錯,這和你在 Angular 中定義一個組件非常類似,實際上,Angular 框架嚴格遵循 Web 組件標準但是為我們簡化了很多事情,所以我們不必自己創建 shadow root 并掛載到宿主元素(注:關于 shadow root 的概念網上資料很多,其實在 Chrome Dev Tools 里,點擊右上角 settings,然后點擊 Preferences -> Elements,打開 Show user agent shadow root 后,這樣你就可以在 Elements 面板里看到很多 DOM 元素下的 shadow root)。然而,我們在 Angular 中創建的組件并沒有注冊為自定義元素,它會被 Angular 以特定方式去處理。如果你對沒有框架時如何創建組件很好奇,你可以查看 Custom Elements v1: Reusable Web Components

現在已經知道,我們可以創建任何一個 HTML 標簽并在模板里使用它。所以,如果我們在 Angular 的組件模板里使用這個標簽,框架將會給這個標簽創建元素定義(注:這是由 Angular Compiler 編譯生成的):

function View_AppComponent_0(_l) {
    return jit_viewDef2(0, [
        jit_elementDef3(0, null, null, 1, "a-comp", [], ...)
    ])
}

然而,你得需要在 module 或組件裝飾器屬性里添加 schemas: [CUSTOM_ELEMENTS_SCHEMA],來告訴 Angular 你在使用自定義元素,否則 Angular Compiler 會拋出錯誤(注:所以如果需要使用某個組件,你不得不在 module.declarationsmodule.entryComponentscomponent.entryComponents 去注冊這個組件):

"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...

所以,我們已經有了 DOM 元素但是還沒有附著在元素上的類呢,那 Angular 里除了組件外還有其他特殊類沒?當然有——指令。讓我們看看指令有些啥。

指令定義

你可能知道每一個指令都有一個選擇器,用來掛載到特定的 DOM 元素上。大多數指令使用屬性選擇器(attribute selectors),但是有一些也選擇元素選擇器(element selectors)。實際上,Angular 表單指令就是使用 元素選擇器 form 來把特定行為附著在 html form元素上。

所以,讓我們創建一個空指令類,并把它附著在自定義元素上,再看看視圖定義是什么樣的:

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

現在 Angular Compiler 在視圖定義函數的第二個參數數組里,添加了新生成的指令定義 jit_directiveDef4 節點,并放在元素定義節點 jit_elementDef3 后面。同時設置元素定義的 childCount 為 1,因為附著在元素上的所有指令都會被看做該元素的子元素。

指令定義是個很簡單的節點定義,它是由 directiveDef 函數生成的,該函數參數列表如下(注:現在 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

本文我們只對 ctor 參數感興趣,它僅僅是我們定義的 ADirective 類的引用。當 Angular 創建指令對象時,它會實例化一個指令類,并存儲在視圖節點的 provider data 屬性里。

所以我們看到組件其實僅僅是一個元素定義加上一個指令定義,但僅僅如此么?你可能知道 Angular 總是沒那么簡單啊!

組件展示

從上文知道,我們可以通過創建一個自定義元素和附著在該元素上的指令,來模擬創建出一個組件。讓我們定義一個真實的組件,并把由該組件編譯生成的視圖工廠類,與我們上面實驗性的視圖工廠類做個比較:

@Component({
  selector: "a-comp",
  template: "I am A component"
})
export class AComponent {}

做好準備了么?下面是生成的視圖工廠類:

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, [], ...)

好的,現在我們僅僅驗證了上文所說的。本示例中, Angular 使用兩種視圖節點來表示組件——元素節點定義和指令節點定義。但是當使用一個真實的組件時,就會發現這兩個節點定義的參數列表還是有些不同的。讓我們看看有哪些不同吧。

節點類型

節點類型(NodeFlags)是所有節點定義函數的第一個參數(注:最新 Angular v5.* 中參數列表有點點不一樣,如 directiveDef 中第二個參數才是 NodeFlags)。它實際上是 NodeFlags 位掩碼(注:查看源碼,是用二進制表示的),包含一系列特定的節點信息,大部分在 變更檢測循環 時被框架使用。并且不同節點類型采用不同數字:16384 表示簡單指令節點類型(注:僅僅是指令,可看 TypeDirective);49152 表示組件指令節點類型(注:組件加指令,即 TypeDirective + Component)。為了更好理解這些標志位是如何被編譯器設置的,讓我們先轉換為二進制:

16384 =  100000000000000 // 15th bit set
49152 = 1100000000000000 // 15th and 16th bit set

如果你很好奇這些轉換是怎么做的,可以查看我寫的文章 The simple math behind decimal-binary conversion algorithms 。所以,對于簡單指令 Angular 編譯器會設置 15-th 位為 1:

TypeDirective = 1 << 14

而對于組件節點會設置 15-th16-th 位為 1:

TypeDirective = 1 << 14
Component = 1 << 15

現在明白為何這些數字不同了。對于指令來說,生成的節點被標記為 TypeDirective 節點;對于組件指令來說,生成的節點除了被標記為 TypeDirective 節點,還被標記為 Component 節點。

視圖定義解析器

因為 a-comp 是一個組件,所以對于下面的簡單模板:

I am A component

編譯器會編譯它,生成一個帶有視圖定義和視圖節點的工廠函數:

function View_AComponent_0(_l) {
    return jit_viewDef1(0, [
        jit_elementDef2(0, null, null, 1, "span", [], ...),
        jit_textDef3(null, ["I am A component"])

Angular 是一個視圖樹,所以父視圖需要有個對子視圖的引用,子視圖會被存儲在元素節點內。本例中,a-comp 的視圖存儲在為 生成的宿主元素節點內(注:意思就是 AComponent 視圖存儲在該組件宿主元素的元素定義內,就是存在 componentView 屬性里。也可以查看 _Host.ngfactory.js 文件,該文件表示宿主元素 的工廠,里面存儲 AComponent 視圖對象)。jit_View_AComponent_04 參數是一個 代理類 的引用,這個代理類將會解析 工廠函數 創建一個 視圖定義。每一個視圖定義僅僅創建一次,然后存儲在 DEFINITION_CACHE,然后這個視圖定義函數被 Angular 用來 創建視圖對象

注:這段由于涉及大量的源碼函數,會比較晦澀。作者講的是創建視圖的具體過程,細致到很多函數的調用。總之,只需要記住一點就行:視圖解析器通過解析視圖工廠(ViewDefinitionFactory)得到視圖(ViewDefinition)。細節暫不用管。

拿到了視圖,又該如何畫出來呢?看下文。

組件渲染器類型

Angular 根據組件裝飾器中定義的 ViewEncapsulation 模式來決定使用哪種 DOM 渲染器:

Emulated Encapsulation Renderer

Shadow Renderer

Default Renderer

以上組件渲染器是通過 DomRendererFactory2 來創建的。componentRendererType 參數是在元素定義里被傳入的,本例即是 jit__object_Object_5(注:上面代碼里有這個對象,是 jit_elementDef3() 的最后一個參數),該參數是渲染器的一個基本描述符,用來決定使用哪一個渲染器渲染組件。其中,最重要的是視圖封裝模式和所用于組件的樣式(注:componentRendererType 參數的結構是 RendererType2):

{
  styles:[["h1[_ngcontent-%COMP%] {color: green}"]], 
  encapsulation:0
}

如果你為組件定義了樣式,編譯器會自動設置組件的封裝模式為 ViewEncapsulation.Emulated,或者你可以在組件裝飾器里顯式設置 encapsulation 屬性。如果沒有設置任何樣式,并且也沒有顯式設置 encapsulation 屬性,那描述符會被設置為 ViewEncapsulation.Emulated,并被 忽略生效,使用這種描述符的組件會使用父組件的組件渲染器。

子指令

現在,最后一個問題是,如果我們像下面這樣,把一個指令作用在組件模板上,會生成什么:

我們已經知道當為 AComponent 生成工廠函數時,編譯器會為 a-comp 元素創建元素定義,會為 AComponent 類創建指令定義。但是由于編譯器會為每一個指令生成指令定義節點,所以上面模板的工廠函數像這樣(注:Angular v5.* 版本是會為 元素多帶帶生成一個 *_Host.ngfactory.js 文件,表示宿主視圖,多出來的 jit_directiveDef6(16384, null, 0, jit_ADirective8, [], ...) 是在這個文件代碼里。可以 ng cli 新建項目查看 Sources Tab -> ng://。但作者表達的意思還是一樣的。):

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, [], ...)

上面代碼都是我們熟悉的,僅僅是多添加了一個指令定義,和子組件數量增加為 2。

以上就是全部了!

注:全文主要講的是組件(視圖)在 Angular 內部是如何用指令節點和元素節點定義的。

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

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

相關文章

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

    摘要:本文主要介紹輸入輸出綁定方式,特別是當父組件輸入綁定值變化時,如何更新子組件輸入值。更新指令的屬性上文中已經描述了函數是用來更新元素的屬性,而是用來更新子組件的輸入綁定屬性,并且變更檢測期間傳入的參數就是函數。 原文鏈接:The mechanics of property bindings update in Angular showImg(https://segmentfault....

    tianhang 評論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
  • [] 關于 `ExpressionChangedAfterItHasBeenCheckedErro

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

    andong777 評論0 收藏0
  • [] 別再對 Angular Modules 感到迷惑

    摘要:大多數初學者會認為也有封裝規則,但實際上沒有。第二個規則是最后導入模塊的,會覆蓋前面導入模塊的。 原文鏈接:Avoiding common confusions with modules in Angular showImg(https://segmentfault.com/img/remote/1460000015298243?w=270&h=360); Angular Modul...

    LMou 評論0 收藏0
  • [] 你真的知道 Angular 單向數據流嗎

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

    fox_soyoung 評論0 收藏0

發表評論

0條評論

LiveVideoStack

|高級講師

TA的文章

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