[Day 30] 微探讨 Angular 的 Event binding 与 Property binding

铁人赛最後一天!

今天要跟各位分享的呢,是昨天与前天 Banana in a box 系列的延伸,关於 Angular 怎麽处理 event 与 property binding。

因为是延伸的内容,所以我会继续以昨天的程序码作为范例!

果然还是要从编译後的内容开始

_angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵdefineComponent"]({
  // ... 略
  consts: [[3, "sliderValue", "sliderValueChange"]],
  template: function AppComponent_Template(rf, ctx) {
    if (rf & 1) {
      _angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵelementStart"](
        0,
        "app-slider-wrapper",
        0
      );
      _angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵlistener"](
        "sliderValueChange",
        function AppComponent_Template_app_slider_wrapper_sliderValueChange_0_listener(
          $event
        ) {
          return (ctx.slider = $event);
        }
      );
      _angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵelementEnd"]();
      _angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵelementStart"](1, "div");
      _angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵelementStart"](2, "label");
      _angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵtext"](3, "Slider value: ");
      _angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵelementEnd"]();
      _angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵelementStart"](4, "label");
      _angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵtext"](5);
      _angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵelementEnd"]();
      _angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵelementEnd"]();
    }
    if (rf & 2) {
      _angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵproperty"](
        "sliderValue",
        ctx.slider
      );
      _angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵadvance"](5);
      _angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵtextInterpolate"](
        ctx.slider
      );
    }
  }
});

↑ Block 1

有关 Event binding

首先从编译後的程序码可以看到有 ɵɵlistener 这个 instruction,而这个 instruction 的实作内容如下:

export function ɵɵlistener(
  eventName: string,
  listenerFn: (e?: any) => any,
  useCapture = false,
  eventTargetResolver?: GlobalTargetResolver
): typeof ɵɵlistener {
  const lView = getLView();
  const tView = getTView();
  const tNode = getCurrentTNode()!;
  listenerInternal(
    tView,
    lView,
    lView[RENDERER],
    tNode,
    eventName,
    listenerFn,
    useCapture,
    eventTargetResolver
  );
  return ɵɵlistener;
}

↑ Block 2

ɵɵlistener 传入的参数除了 eventName(本例是 sliderValueChange)、listenerFn 之外,还可以传入 useCapture 与 eventTargetResolver,後两者除非是有特殊需求,否则不会用到。

将 eventName 与 listenerFn 传入之後 Angular 会透过 getLViewgetTViewgetCurrentTNode 方法来取得当前的 LView、TView 与 TNode。

最後则是呼叫 listenerInternal 函式(Link)并将相关的资讯传递给它,来做以下的事:

  1. 判断 tNode 是不是 HTML Element。
  2. 判断 renderer 是属於 ObjectOrientedRenderer3 还是 ProceduralRenderer3,不同的 renderer 会有不同的处理方式。
  3. 依据 renderer 的种类将 event listener 绑到 element 上,如果是 ObjectOrientedRenderer3 的话。就采用 HTMLElement 的 addEventListener 方法来绑,如果是 ProceduralRenderer3 的话就以 renderer 实作的方式来绑。
  4. Subscribe directive 的 Output。

有关 Property binding

Angular 会在 update block 的时候呼叫 ɵɵproperty instruction 来处理 property 相关的事情,以下是它的实作内容(Link):

export function ɵɵproperty<T>(
    propName: string, value: T, sanitizer?: SanitizerFn|null): typeof ɵɵproperty {
  const lView = getLView();
  const bindingIndex = nextBindingIndex();
  if (bindingUpdated(lView, bindingIndex, value)) {
    const tView = getTView();
    const tNode = getSelectedTNode();
    elementPropertyInternal(
        tView, tNode, lView, propName, value, lView[RENDERER], sanitizer, false);
    ngDevMode && storePropertyBindingMetadata(tView.data, tNode, propName, bindingIndex);
  }
  return ɵɵproperty;
}

↑ Block 3

ɵɵproperty 在执行更新的操作之前,会先透过 bindingUpdated 函式来确认传入的值跟先前在 lView 内纪录的值是否相同,如果有变动的话才会执行 elementPropertyInternal 函式来更新 property,否则的话就直接回传 ɵɵproperty instruction,让 Angular 可以透过 ɵɵproperty('a', 'v1')('b', 'v2') 这样的方法来设定 property binding。


以上就是今天的「你可以不用知道的 Angular」!简洁扼要的说明了 Angular 是怎麽实作 event binding 与 property binding。

完赛心得

30 天,有够累。

主要是因为自己筹备期间因为刚开始翻阅 Angular 原始码,艰涩难懂到让我一直发懒,所以直到正式开赛前一天才觉得应该拟定每天的内容,接着就开始了每天绞尽脑汁产出一篇的生活。其中有几篇真的是困难重重,像是最开头的 Dependency Injection 系列,那个原始码我真的是迟迟无法参悟阿……

总之感谢各位读者这 30 天的陪伴,因为比赛时间限制,所以我做了一些艰难的抉择,像是不小心忽略部分说明之类的。

发布在铁人赛的这系列未来不会有其他更新,但我会在个人的 Medium(这里)逐步将内容完善!热烈欢迎有兴趣的读者跟我一起转移阵地噢!

再次感谢!


<<:  Day 30 - 使用 CDK 创建 Open Unlight 游戏

>>:  [DAY-30] 最後一幕只是故事的结尾,你也知道故事没有结尾是行不通的。

Day10 - 物理模拟篇 - 弹跳球世界I - 成为Canvas Ninja ~ 理解2D渲染的精髓

作为物理模拟开场的第一进程,当然就要来讲一下最经典的物理模拟案例:『弹跳球』~ 其实很多国外的Can...

Day 6:常见的CSS tag+应用

上一篇,我们学到了如何用CSS选取HTML物件,但我们只选取了HTML物件,而没有改变HTML物件的...

[Day 29]-【STM32系列】实作-步进马达 + ULN2003 控制

今天介绍步进马达,疑?昨天不是才说过马达吗?昨天的是伺服马达,今天的则是伺服马达 这两者有甚麽不一样...

【D3】发现厨房不能用,需要更换厨房环境: 使用Python 3.8

前言 以为很顺畅的,结果发现Python 3.9无法支援,那就只好用3.8罗!因为采了不小的雷,因此...

终於...下载到了比赛的资料了.....

在大大们的留言下 好心告诉我在哪边 我终於找到了 原来还要特别去永丰的网站填资料申请才会寄到信箱 今...