Angular 双向绑定的二三事

Angular 双向绑定

利用一个例子来看看破坏双向绑定

// parent component html
<child [val]='val'/>
<button (onClick)="val=1">Reset</button>


// child component html
<span>{{val}}</span>
<button type="button" (onClick)="val=val+1">increase</button>

parent component 传递一个val给child component, 同时child component 内部有个button ,点击会加1,那么现在进行如下操作,首先,val 的值是1, 然后,我们点击 child component 内部的button, 最后,我们点击parent component内部的button reset. 那么val 值在两个组件中的值为多少,显示为多少。 大家猜猜,结果看下图。

步骤 val in parent val in child val in display
初始 1 1 1
点击btn in child 1 2 2
点击btn in parent 1 2 2
为什么,Angular 怎么不工作了
先思考这几个问题
  • Angular 是如何发现组件的差别,从而触发更新的
    Angular 在每个组件内部(具体放在哪里的还得看源码,但是肯定是跟组件对象instance 关联),缓存了所有的Input, 每次在检查组件是否要更新时,都会用最新的Input 更缓存的Input 进行比较,找到差异,差异在ngOnChanges() hook 函数内体现。有两个问题,
    • 比较什么,
      以上面的例子为例,假设我们正在对parent component 进行changeDection, 首先,我们比较的东西是<child [val]='val'/> 里面的 ='val' 计算的结果。所以,在parent component 内部肯定缓存了它之前的值。比较之前的值跟最新的值得差异,从而决定是否将哪些Input 更新到Child component 的,同时也会刷新其在parent component 的缓存。所以缓存的数据结构肯定类似于{componentInstance:{...allInputs}}, 如果发现差异,只会将差异更新到childComponent, 我们可以通过在child component 内部的ngOnChanges(changes:SimpleChanges)拿到需要更新的Inputs。
    • 怎么比较?
      我猜跟React 里面类似吧,说白了就是对象比较引用,primitive type 直接值比较

那我们来分析一下上面例子。

步骤 val cache of child in parent val in child
初始化后 1 1
点击increase in child 1 2
点击reset in parent 1 2
在我们点击increase in child 后,在parent component 进行changedetection 时,val 在parent 内部,已经缓存的值都是1, 所以,对于parent component 而言,child component 的Input 没有变化,当我们点击reset 时同样的,val在parent 内部还是1,跟缓存的值一样,所以,child component Input 还是没有变化,Angular不会将1赋值给child component, 但是child component 内部 val 值一直是2, 这就是为什么Angular 不工作的原因。

怎么解决这个问题?

问题的根源是在parent 内部,child Input val 缓存的值跟child 内部实际的val 值不一致。那么我们有两个思路,

  • 第一种,我们只要告诉Angular 或者,让Angular感知到val 变化了,看如下代码
<button (onClick)="val=3;setTimeout(()=>val=1,0)">Reset</button>

我们先把val设置成一个第三状态值,然后,异步再设置成我们想要的值,先设置成第三状态值,是为了区别于缓存的1,然后Angular 更新值缓存,接着后面的setTimeout,把值再设置成我们要的值,Angular 再次介入,更新。这是一种hack的方式。

  • 第二种方法,我们利用双向绑定,也就是child 的内部不会直接更新val 的值,而是把更新的值通知parent ,由parent 来更新,然后在changedetection 里面,child 拿到更新后的值,更新DOM, 如果有同学熟悉React 的话,这就是controlled component 跟uncontrolled component。Angular 里面提供了语法糖,不用像React 那么累。看代码
// parent component
<child [(val)]='val'/>

// child component ts
@Input()
val:any;
@Output()
valChange:EventEmitter<any>=new EventEmitter<any>()

increase(){
this.valChange.emit(this.val+1);
}


<button type="button" (onClick)="increase()">increase</button>

当点击child 里面的increase button 时,child component 并不直接修改val,而是把修改后的值通知parent,parent利用语法糖,自动更新val ,在随后的changedetection中,把更新后的值赋给child.
this.valueChange.emit()是不是实时的? 答案是:是的

如果Input 不是一个primitive type,而是一个对象,会怎么样,如果是对象一些就简单多了,因为对象是地址引用的比较,所以只有引用不变,Angular 一律认为没有改变。

这里强调一点,尽管Angular 发现一些组件的Input 没有发生变化,Angular 还是会递归的对其内部的组件做ChangeDetection,以及Dom的刷新,ChangeDetection 的目的是发现差异,并且将差异传递给目标组件,如果目标是native html element, 那么就是更新Dom,如果是component,direcrive, 那么就更新Input,然后递归ChangeDetection.(不管目标组件有没有Input 更新,都会做,当然有特殊,例如Onpush,等)

posted @ 2021-06-16 22:24  kongshu  阅读(138)  评论(0编辑  收藏  举报