温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

Angular中如何实现变更检测

发布时间:2021-08-11 11:06:45 来源:亿速云 阅读:141 作者:小新 栏目:web开发

小编给大家分享一下Angular中如何实现变更检测,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!

一、Angular 的 DOM 更新机制

我们先来看一个最简单地 demo

Angular中如何实现变更检测

在按钮被点击时,我们改变了组件的 name 属性,就在一瞬间 DOM 中也显示出了改变后的值,这似乎有些“神奇”。

Angular中如何实现变更检测

如果紧跟着元素更改的语句之后打印出真实 DOM 中的 innterText,却发现仍然还是旧的值,可是明明视图中的值已经改变了,这两段代码中到底发生了什么呢?如果你也对此也疑惑不已,那么就和我一起来揭晓这个答案。

我们仔细回忆下刚刚发生的事情:

  • 点击按钮

  • 值改变

如果使用原生 JS 来编写这段代码,那么点击按钮后的视图肯定不会发生改变,而在 Angular 中却让视图发生了改变。如果你对 Angular 有稍微深入的了解,就会知道一个叫做 zone.js 的库,仔细翻看就会发现,zone.js 对所有可能发生值改变的事件做了一层处理,比如:

  • Events:click,mouseover,mouseout,keyup,keydown 等所有的浏览器事件

  • Timer:setTimeout,setInterval

  • XHR:各类请求等

Angular 还为我们提供了禁用 zone.js 的方法。

Angular中如何实现变更检测

Angular中如何实现变更检测

禁用 zone 后,当我们再次点击按钮时,视图未更新。

带着好奇心,我们找到 Angular 源码中视图更新的关键代码

Angular中如何实现变更检测

这一次我们手动在代码调用这个方法。

Angular中如何实现变更检测

Angular中如何实现变更检测

果然和预料中的一样!视图更新了,更让人惊喜是,打印出来的 innerText 也更新了!

到这里,我们得出了一个结论,DOM 的更新依赖于 tick() 的触发,zone.js 帮助开发者省去了手动触发的操作

好了,小试牛刀之后,接下里我们就来仔细探究 Angular 视图更新的背后到底发生了什么。

二、窥探变更检测的秘密

1.从一个常见的 Error 说起

我们先来看这样一处错误,在 child 组件的 ngOnInit 中更改了父组件 parent 的 name 值,结果出现了大家一定都遇到过的错误信息

Angular中如何实现变更检测

Angular中如何实现变更检测

可是这样写并不是每次都会报错,例如我们去掉子组件 child 的输入属性,刷新一下,发现同样的代码却可以运行,父组件的 name 可以被正常更改。

Angular中如何实现变更检测

emmm... 陷入沉思...

也许你和刚开始学习 Angular 时的我一样,在 stackoverflow 里搜索这个问题,复制了个自己也不知道为什么能起作用的代码就直接粘贴了上去,后面再遇到这个问题时,继续在 stackoverflow 里搜索和复制粘贴,如此反复...

随着时间的推移,精通各种 CRUD 的你越来越不满足于这种面向 stackoverflow 编程的自己,开始在社区、文档、论坛上不停的查找问题的答案,但是看完他们的回答和文章,好像只知道了有个叫做变更检测的东西,但是具体是怎么导致了这个 bug ,却支支吾吾的说不太清楚,如果你也和我一样对上述经历深有体会,那么就继续向下探寻真相吧!

2.说了半天的变更检测到底是什么?

当我们在 model 中改变数据时,框架层需要知道:

  • model 哪里发生了改变

  • view 中哪里需要更新

React 中的 Virtual Dom 大家一定都不陌生,React 通过对比 DOM 的新状态与旧状态来决定更新哪一部分 dom,而不是更新所有的 dom,这也是 Angular 中变更检测(change detection)的异曲同工之处。

整个 Angular 应用是个组件树,不可能任意一个组件中的改变都触发所有组件的更新,这样效率太低也太耗时,例如用户更改了某个 button 的状态,那么最理想的做法是只更新这个 button 的样式或文字,而不是整个应用全部更新一遍,变更检测的目的也就是为此。

默认情况下(ChangeDetectionStrategy.Default),父组件的变更检测发生时,子组件也会触发变更检测。

Angular中如何实现变更检测

(CD 即为 changeDetection )

每次变更检测时,都会比较新旧状态,如果两次变更检测(开发环境下)的结果不一致就会报错,例如:

Expression has changed after it was checked

这也就解释了为什么在子组件中更改了父组件的值会报错。

但是!在前面的两个例子中我们都在子组件中更改了父组件的值,只有第一个报错,第二个是可以正常更新的,如果你也同样很疑惑这中间真正的差异点在哪里,那么接着往下阅读吧~

3.问题的关键 — 检测顺序 Detection Sequence

先上结论:

  • 更新所有子组件的绑定属性

  • 调用所有子组件的 OnChanges,OnInit,DoCheck,AfterContentInit 生命周期钩子

  • 更新当前组件的 DOM

  • 子组件触发变更检测

  • 调用所有子组件的的 AfterViewInit 的生命周期钩子

这里我们不关注于太细的细节(不用好奇为什么是这样的顺序,只要记住 Angular 里就是这样设定的就可以了,如果有大佬想谈谈 Angular 在这部分的设计思想,欢迎在评论区留言探讨~)

第一个例子中,父组件 parent 给子组件 child 传入了输入属性 name,且子组件在 ngOnInit 中更新了父组件的 name 属性,也就是说这段代码**违背了检测顺序(**在顺序的第二步中操作了第一步)!

<p>{{ name }}<p>
<child [name]="name"></child>

而在第二个例子中,就算子组件在 ngOnInit 中也更新了父组件的 name 属性,但是由于父组件parent 中没有给子组件 child 绑定输入属性 name,不会出现与违背变更检测队列顺序的情况,所以就可以正常运行。

<p>{{ name }}<p>
<child></child>

这个时候再去看看 stackoverflow 上的高赞回答 是不是就清晰明了很多,按照上述的检测顺序,我们会发现只要父组件中对子组件做了属性绑定,不管是在 OnChanges,OnInit,DoCheck,AfterContentInit 和 AfterViewInit 中的任意一个声明周期钩子中执行下述代码都会报错。

this.parentCmpt.name = 'child'

好了,到这里我们已经明白了这种错误发生的真正原因,但是我还是要提醒一下,这种错误只会在开发环境下触发,生产环境下会调用 enableProdMode() ,变更检测次数会从 2 降到 1,这部分在 Angular 源码当中也有描述。

Angular中如何实现变更检测

当然你不能因为这个 bug 就强制在开发环境下使用生产模式...

4.大家常说的 ChangeDetectionStrategy.OnPush 又是什么?

ChangeDetectionStrategy 默认为 Default,也就是父组件的 CD 会触发子组件的 CD,但是很显然有些情况下我们可以自行判断出某些子组件在父组件 CD 时并不用触发,而 OnPush

则是 Angular 为开发者提供的一便捷操作方式。

Angular中如何实现变更检测

用动图来表示就是:查看链接

Angular中如何实现变更检测

知名的 Angular 开源组件库 ng-zorro 就使用了大量的 OnPush 策略,这也是 Angular 性能优化的方法之一。

Angular中如何实现变更检测

三、再深入了解一些

Angular 给每个组件都关联了一份组件视图,通过 ChangeDetectorRef 可以拿到相关联的视图,在定义中我们可以看到:

export declare abstract class ChangeDetectorRef {
    abstract checkNoChanges(): void;
    abstract detach(): void;
    abstract detectChanges(): void;
    abstract markForCheck(): void;
    abstract reattach(): void;
}

1.detach 和 reattach

观察下面的动图,被 detached 的组件将不会被检查变更。

Angular中如何实现变更检测

reattach 则可以让被 detached 的组件重新可以被检测到变更。

2.markForCheck

reattach 只会重新启用对当前组件的变更检测,但是如果父组件没有启动变更检测,那么 reattach 并不会起作用,而 markForCheck 可以很好地解决这个问题。

这一点在 ng-zorro 的源码中可以了解一二。

例如在 nz-anchor 组件中更改 nz-anchor-link 组件的 active 属性时,由于本身 ChangeDetectionStrategyOnPush ,那么就需要激活 markForCheck 来重新启用检测。具体写法可以查看 github 中的源代码。

  • nz-anchor.component.ts

  • nz-anchor-link.component.ts

用动图来展示则是这样,注意观察设置了 MFC 的前后变化

Angular中如何实现变更检测

3.detectChanges

这个方法如同字面意思一样很好理解,就是触发一次变更检测啦,还记得本文中的第一个例子吗,我们不手动触发 tick() ,而是触发 detechtChanges() 也是可以达到效果的。

Angular中如何实现变更检测

Angular中如何实现变更检测

以上是“Angular中如何实现变更检测”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注亿速云行业资讯频道!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI