iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 12
0
Modern Web

從巨人的 Tip 看 Angular系列 第 12

[Day 12] 深度探討 Angular 解析 Attribute directive 的背後流程

  • 分享至 

  • xImage
  •  

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。

https://ithelp.ithome.com.tw/upload/images/20200927/201291489OEPefagjv.png

↑ 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 在背後為你做的所有事情!

?

以下按照入團順序列出我們團隊夥伴的系列文章!


上一篇
[Day 11] 關於 @Self、@Optional、@SkipSelf 的二三事與 @Host 的陷阱
下一篇
[Day 13] exportAs,透過 template variable 取得 directive 實體
系列文
從巨人的 Tip 看 Angular30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言