通過單步除錯的方式學習 Angular 中 TView 和 LView 的概念
問題描述
本文涉及到的程式碼位置:https://github.com/wangzixi-diablo/ngDynamic
看這樣一組 parent Component 和 child Component:
@Component({ selector: 'child', template: `<span>I am a child.</span>` }) export class ChildViewComponent {} @Component({ selector: 'parent', template: ` <div> projected content: <ng-content></ng-content> </div> ` }) export class ParentComponent {} @Component({ selector: 'demo', template: ` <parent id="p1"> <child id="c1"></child> </parent> <child id="c2"></child> ` }) export class Demo {}
其中 parent Component 定義了一個內容投影區域,通過下列高亮程式碼,將子 Component 的內容投射進來:
即該區域:
最後的執行時效果:
問題分析
當上述應用被 load 但尚未完成 bootstrap
時,Ivy 將其解析為如下的 TView
:
const tChildComponentView = new TView( tData: [ new TElementNode(‘span’), new TTextNode(‘I am a child.’), ], …, ); const tParentComponentView = new TView( tData: [ new TElementNode(‘div’), new TTextNode(‘projected content: ‘), new TProjectionNode(), ], …, ); const tDemoAppView = new TView( tData: [ new TElementNode(‘parent’, [‘id’, ‘p1’]), new TElementNode(‘child’, [‘id’, ‘c1’]), new TElementNode(‘child’, [‘id’, ‘c2’]), ], …, )
接下來的步驟就是 bootstrap
,具體的邏輯就是基於 TView
建立 LView
:
const lParentComponentView_p1 = new LView( tParentComponentView, new ParentComponent(…), document.createElement(‘div’), document.createText(‘projected content: ‘), ); const lChildComponentView_c1 = new LView( tChildComponentView, new ChildComponent(…), document.createElement(‘span’), document.createText(‘I am a child.’), ); const lChildComponentView_c2 = new LView( tChildComponentView, new ChildComponent(…), document.createElement(‘span’), document.createText(‘I am a child.’), ); const lDemoAppView = new LView( tDemoAppView, new DemoApp(…), document.createElement(‘parent’), lParentComponentView_p1, document.createElement(‘child’), lChildComponentView_c1, document.createElement(‘child’), lChildComponentView_c2, )
上述邏輯在偵錯程式裡如下圖所示:
其中 JerryAppComponent
被維護為 bootstrap
Component:
上述程式碼展示了 TView
和 LView
二者的區別。
再看 ChildComponent,TView 的例項只有一個,而 LView 的例項卻有兩個,因為 ChildComponent 被使用了兩次。 另一個關鍵區別是 LView 只儲存特定於該元件例項
的資料——例如元件例項和關聯的 DOM 節點
。 TView 儲存在元件的所有例項之間共享的資訊——例如需要建立哪些 DOM 節點
。
在上面的示例中,LView 顯示為一個類(new LView(...)。實際上,我們將它儲存為一個數組([...])。將 LView 儲存為一個數組是出於記憶體效能的原因。 每個模板都有不同數量的 DOM 節點和子元件/指令,將其儲存在陣列中是最有效的方式。
使用陣列進行儲存的含義是不清楚在陣列中的哪個位置儲存例項資料。 TData 用於描述 LView 中每個位置儲存的內容。 所以 LView 本身不足以推理,因為它在沒有上下文的情況下儲存值。 TView 描述了元件需要什麼,但它不儲存例項資訊。 通過將 LView 和 TView 放在一起,Ivy 可以訪問和推理 LView 中的值。 LView 儲存值,而 TView 儲存 LView 中值的含義,類似元資料
或者 schema
的概念。
為簡單起見,LView 僅儲存 DOM 節點。 在實踐中,LView 還儲存繫結、注入器、淨化器以及與檢視狀態相關的任何其他內容(在 TView/TData 中具有相應的條目。)
總結
考慮 View 和 View 的一種方法是將這些概念與面向物件的程式設計聯絡起來。 TView 就像類,而 View 就像類例項。