OK!周末快樂,今天要也不是要分享 Tip 啦,今天要分享的是 Directive decorator(@Directive)的解析流程。前一陣子很常提到的是 Component decorator(@Component),當然也要提一下它的兄弟 @Directive 囉!
以下是一個簡單的範例:
// HTML
<div appMydir>
<p>app works!</p>
</div>
↑ Block 1:AppComponent HTML Template
@Directive({
selector: '[appMydir]',
})
export class MydirDirective implements OnInit {
constructor(private ele: ElementRef<HTMLDivElement>) {}
ngOnInit(): void {
const pEle = document.createElement('p');
pEle.textContent = 'myDir works!';
this.ele.nativeElement.appendChild(pEle);
}
}
↑ Block 2:MydirDirective
這個範例的行為非常單純,在 AppComponent 的 div element 上新增一個 attribute directive appMydir
,當 render 完成之後,就會在 app works! 這個 p element 下方看到一個由 appMydir 這個 directive 建立的 p element。
↑ Image 1:render 後的結果
但這邊為止是 directive 的簡單應用範例,接下來就要開始翻 code 囉!
AppComponent.ɵcmp = _angular_core__WEBPACK_IMPORTED_MODULE_0__[
"ɵɵdefineComponent"
]({
type: AppComponent,
selectors: [["app-root"]],
decls: 3,
vars: 0,
consts: [["appMydir", ""]],
template: function AppComponent_Template(rf, ctx) {
if (rf & 1) {
_angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵelementStart"](0, "div", 0);
_angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵelementStart"](1, "p");
_angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵtext"](2, "app works!");
_angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵelementEnd"]();
_angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵelementEnd"]();
}
},
directives: [_mydir_directive__WEBPACK_IMPORTED_MODULE_1__["MydirDirective"]],
styles: [
"\n/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiIsImZpbGUiOiJzcmMvYXBwL2FwcC5jb21wb25lbnQuc2NzcyJ9 */",
],
});
↑ Block 3:AppComponent 經過 compile 後的 JavaScript
滿特別的一個部分是 appMydir
這個 attribute directive 其實沒有被放在 div 的身上,而是被放在一個叫做 consts 的屬性上。
關於 consts 這個屬性是什麼,我們可以在這邊找到,基本上這個屬性會將這一個 component 的 template 有提到的 attribute 都放進這個屬性。以本例來說,因為 appMydir 是一個 attribute directive 的緣故,所以也會放進這個屬性。
那下一個問題就是,div 這個 html element 是怎麼找出他身上還有一個 attribute 的呢?
我們可以看一下 ɵɵelementStart
這個函式:
export function ɵɵelementStart(
index: number, name: string, attrsIndex?: number|null, localRefsIndex?: number): void {
const lView = getLView();
const tView = getTView();
const adjustedIndex = HEADER_OFFSET + index;
// ... 下略
}
↑ Block 4:一半的 ɵɵelementStart 函式
快速看一下這個函式需要的傳入參數,第三個參數就是 attrsIndex
,也就對應到前面 AppComponent definition 的 consts 屬性的 index。
然後再往下看一下 ɵɵelementStart 函式的內容:
const tNode = tView.firstCreatePass
? elementStartFirstCreatePass(
index,
tView,
lView,
native,
name,
attrsIndex,
localRefsIndex
)
: (tView.data[adjustedIndex] as TElementNode);
↑ Block 5:Get tNode (ɵɵelementStart)
建立 tNode 時就會將 attrsIndex 傳入建立 element 的 elementStartFirstCreatePass 函式:
function elementStartFirstCreatePass(
index: number, tView: TView, lView: LView, native: RElement, name: string,
attrsIndex?: number|null, localRefsIndex?: number): TElementNode {
ngDevMode && assertFirstCreatePass(tView);
ngDevMode && ngDevMode.firstCreatePass++;
const tViewConsts = tView.consts;
const attrs = getConstant<TAttributes>(tViewConsts, attrsIndex);
const tNode = getOrCreateTNode(tView, lView[T_HOST], index, TNodeType.Element, name, attrs);
const hasDirectives =
resolveDirectives(tView, lView, tNode, getConstant<string[]>(tViewConsts, localRefsIndex));
// ... 略
return tNode;
}
↑ Block 6
取得 attribute 的相關資訊並放入 tNode 內的行為,就是由 Block 6 中的 elementStartFirstCreatePass 函式來處理的。
以上,就是當你在 HTML Template 上加入 attribute directive 時,Angular 在背後為你做的所有事情!
以下按照入團順序列出我們團隊夥伴的系列文章!