博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Angular 进阶:从源码理解@Input绑定是如何被编译和实现的
阅读量:5879 次
发布时间:2019-06-19

本文共 5344 字,大约阅读时间需要 17 分钟。

阅读本文需要已经对ngc输出的代码、Angular packages/core源码有所熟悉。

这是我搭建的一个直接可以使用的,具体功能和用法可以看README,我认为它对于深入学习Angular源码十分有帮助。本文也使用这个项目开始做实验。

另外需要注意的一点是,Component是一种特殊的(带有view的)Directive,本文的讨论完全适用于Component。

directive inputs

等价于在Class中使用@Input装饰器,Angular Compiler输出的代码完全相同。

Directive inputs的本质是:将Directive实例对象中的某个property与父视图(parent view)中的某个表达式进行数据绑定,在每个变化检测周期,比较这里两个值是否相等,如果不相等,则更新Directive实例对象中的这个property。

其他类型的数据绑定也是类似的,比如绑定template中某个普通HTML元素的id、class。

我创建了一个最基本的demo仓库来展示directive的input是如何实现的,读者可以克隆下来自己根据README指引用ngc编译:

输入命令行指令npm run dev,ngc为AppComponent的view输出以下代码:

==>

export function View_AppComponent_0(_l) { return i1.ɵvid(0, [(_l()(), i1.ɵeld(0, 0, null, null, 1, "b-comp", [["account-id", "attribute binding value"]], null, null, null, i2.View_BComponent_0, i2.RenderType_BComponent)), i1.ɵdid(1, 49152, null, 0, i3.BComponent, [], { id: [0, "id"] }, null)], function (_ck, _v) { var _co = _v.component; var currVal_0 = _co.bindingVal; _ck(_v, 1, 0, currVal_0); }, null); }

[["account-id", "attribute binding value"]]表示在这个元素上的设置了attribute。注意,当property binding与attribute同时匹配一个directive的输入时,property binding优先作为输入。我在template中进行account-id='attribute binding value'attribute初始化仅仅是为了说明这一点,接下来可以删掉这个绑定了。

务必要区分“
初始化 HTML attribute”(比如
account-id="attribute binding value")与“绑定 DOM property”(绑定 DOM property 有两种方式:
[account-id]="bindingVal"
account-id="{
{bindingVal}}"
,注意1. 这两种property binding的编译输出有区别;2. 第二种property binding的形式与“初始化 HTML attribute”很相似,区别在于有没有双花括号)。官方文档: 。

另外,Angular 其实也能绑定HTML attribute。[attr.account-id]='"attribute binding value"'和上面初始化attribute的效果相同,但是绑定更加强大,你可以将它与component中的一个property绑定,使attribute随着property更新。如果你的CSS中有[attribute=value]这样的CSS选择器,HTML attribute binding或许可以帮到你(这种情况比较少)。大多数情况下,我们仅仅需要初始化element或directive的attribute。

{ id: [0, "id"] }在中被转化成了property binding的记号(flags: BindingFlags.TypeProperty),它表示了当前directive node的实例对象中的idproperty需要被绑定更新

但是什么时候更新呢?用什么数据来更新呢?NodeDef并没有定义这些,也不应该定义这些,根据,单个NodeDef只负责定义这个Node的属性和行为,而“什么时候更新、用什么数据来更新”已经超越了这个node的范畴,它们由来指定。
确实,从ngc输出的代码中,我们看到这个参数是

function (_ck, _v) { var _co = _v.component; var currVal_0 = _co.bindingVal; _ck(_v, 1, 0, currVal_0); }
  1. 用vscode追踪一下,很快就能发现这个函数被在了中。
  2. 然后,会调用ViewDefinition.updateDirectives函数,并根据checkType提供不同的参数,不妨假设提供的参数是(prodCheckAndUpdateNode, view),也就是说,function (_ck, _v)的实参是它。
  3. 好,调用ViewDefinition.updateDirectives的实参已经确定了,那么调用它会发生什么呢?前两个语句var _co = _v.component; var currVal_0 = _co.bindingVal; 很简单:_co是当前view的component实例(也就是AppComponent的实例,即Model-View-Whatever架构模式中的Model),currVal_0是Model中的一个数据。这就回答了“用什么数据来更新呢”的问题,用AppComponent(parent component)实例的bindingVal来更新BComponent(child directive)的@input property。
    检查和更新绑定的逻辑都在第三个语句_ck(_v, 1, 0, currVal_0);。我们前面已经说过了,_ck的实参是。注意到_ck的返回值没有被使用,所以可以忽略prodCheckAndUpdateNode的return语句。
  4. prodCheckAndUpdateNode的作用仅仅是利用viewcheckIndex参数来获取具有绑定的那个node(checkIndex为1也就表示i1.ɵdid(1, 49152, null, 0, i3.BComponent, [], { id: [0, "id"] }, null)这个directive node),然后把锅全部丢给了。
  5. checkAndUpdateNode的作用仅仅是根据argStyle决定传递参数的方式,要一个一个地传递参数还是传入一个数组(前者速度更快,但最多只能传10个value)。假设传递一个数组,也就是说checkAndUpdateNode决定要调用。
  6. checkAndUpdateNodeDynamic中,判断需要更新的node的类型,然后根据node类型调用不同的处理函数。在这个例子中是directive node,也就是说要调用。
  7. 到了checkAndUpdateDirectiveDynamic,我们终于看到directive property更新的逻辑了:
export function checkAndUpdateDirectiveDynamic(    view: ViewData, def: NodeDef, values: any[]): boolean {  const providerData = asProviderData(view, def.nodeIndex);  const directive = providerData.instance;  let changed = false;  let changes: SimpleChanges = undefined !;  for (let i = 0; i < values.length; i++) {    if (checkBinding(view, def, i, values[i])) {      changed = true;      changes = updateProp(view, providerData, def, i, values[i], changes);    }  }  if (changes) {    directive.ngOnChanges(changes);  }  if ((def.flags & NodeFlags.OnInit) &&      shouldCallLifecycleInitHook(view, ViewState.InitState_CallingOnInit, def.nodeIndex)) {    directive.ngOnInit();  }  if (def.flags & NodeFlags.DoCheck) {    directive.ngDoCheck();  }  return changed;}

  1. 先从viewdata获取到这个directive的实例(BComponent实例):

    const providerData = asProviderData(view, def.nodeIndex);const directive = providerData.instance;
    为什么directive和provider扯上了关系?你应该知道在child directive中可以通过依赖注入获取parent directive实例,这都是因为
    Angular将directive看作一种服务,这种服务由宿主元素提供!这也是为什么directive node必须是某个element node的直接孩子。
  2. 对于这个directive的每个input binding,检查绑定是否已经不一致(脏)。如果有,则并记录这次更新在changes中。

    这个函数有一个地方比较有意思:
    如果child node是使用变化检测策略的component,那么updateProp的调用(也就是说,有input binding被更新)会使这个component的view 。可以料想到,如果这个OnPush component没有input binding更新,它的view不会被检查。
    如果将变化检测看作是对
    由若干个view组成的树的深度优先遍历,那么Angular可以通过“剪枝”(不检查OnPush component view以及它的child view)来优化变化检测的速度。
    一个很常见的误解是:Angular在检查到一个directive时才去检查它的input binding,但这是错的。对
    所有child directive的input binding进行脏检查是检查
    parent view时的工作之一。检查完
    parent view以后再检查
    child view。这篇文章有所说明: 。
  3. 如果条件合适,调用这个direvtice的Lifecycle Hooks:ngOnChanges, ngOnInit, ngDoCheck。

    ngDoCheck Lifecycle Hooks的作用主要是针对OnPush component的。在ngDoCheck中扩展基本的脏检查算法。如前文所说,Angular只检查directive的input bingdings是否更新,如果有更新才将OnPush component标记为“将要检查view”。但如果input是一个对象,且发生变化的是对象中的一个property,那么默认Angular脏检查算法无法检测到这种变化,因为input始终是同一个对象引用。这时候你需要在ngDoCheck中自己检查input的某些property,如果发现脏绑定,用 手动将本component标记为“将要检查view”。

好,现在我们已经知道Service.updateDirectives会调用ViewDefinition.updateDirectives函数来检查和更新child directive的input binding。那么这种更新发生在什么时候?也就是说,Service.updateDirectives自己是什么时候被调用的?被谁调用的?

答案是,这个函数是变化检测的一个关键函数,有很多需要整理,我将在另一篇文章中讨论。

更多阅读

转载地址:http://zjcix.baihongyu.com/

你可能感兴趣的文章
ansible 基本操作(初试)
查看>>
更改tomcat的根目录路径
查看>>
51nod 1292 字符串中的最大值V2(后缀自动机)
查看>>
加快ALTER TABLE 操作速度
查看>>
学习笔记之软考数据库系统工程师教程(第一版)
查看>>
PHP 程序员的技术成长规划
查看>>
memcached 分布式聚类算法
查看>>
jquery css3问卷答题卡翻页动画效果
查看>>
$digest already in progress 解决办法——续
查看>>
虚拟机 centos设置代理上网
查看>>
Struts2中Date日期转换的问题
查看>>
mysql 数据类型
查看>>
Ubuntu 设置当前用户sudo免密码
查看>>
设置tomcat远程debug
查看>>
android 电池(一):锂电池基本原理篇【转】
查看>>
Total Command 常用快捷键
查看>>
ionic 调用手机的打电话功能
查看>>
怎么使用阿里云直播服务应用到现在主流直播平台中
查看>>
Xcode全局替换内容,一键Replace
查看>>
1000 加密算法
查看>>