带你详细了解Angular中的变更检测

本篇文章带你详细了解一下Angular中的变更检测,给大家介绍一下Angular 的 DOM 更新机制、变更检测能解决什么问题等。

带你详细了解Angular中的变更检测

通过这篇的文章,可以帮助你收获这些知识:

Angular 的 DOM 更新是如何做到的?【相关教程推荐:《angular教程》】变更检测解决了什么问题?更深入了解变更检测,熟悉它在 Angular 源码中的定义以及在 ng-zorro 中的使用以及一点个人感悟

一、Angular 的 DOM 更新机制

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

1.gif

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

2.gif

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

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

点击按钮

值改变

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

Events:click,mouseover,mouseout,keyup,keydown 等所有的浏览器事件Timer:setTimeout,setIntervalXHR:各类请求等

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

3.png

4.gif

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

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

5.png

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

6.png

7-1.gif

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

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

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

二、窥探变更检测的秘密

1.从一个常见的 Error 说起

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

8.png

9.png

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

10.png

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),父组件的变更检测发生时,子组件也会触发变更检测。

11.png

(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 属性,也就是说这段代码**违背了检测顺序(**在顺序的第二步中操作了第一步)!

{{ name }}

登录后复制登录后复制

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

{{ name }}

登录后复制登录后复制

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

this.parentCmpt.name = 'child'

登录后复制

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

12.png

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

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

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

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

13.png

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

14.gif

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

15.png

三、再深入了解一些

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 的组件将不会被检查变更。

16.gif

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

2.markForCheck

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

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

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

nz-anchor.component.tsnz-anchor-link.component.ts

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

17.gif

3.detectChanges

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

18.png

19.gif

四、最后

到这里,我相信大家已经基本弄明白了 Angular 变更检测,如果有任何疑问,欢迎在评论区交流讨论~

在撰写这篇文章时,笔者参(fu)考(zhi)了大量的社区文章和讨论,一方面是感慨如此重要的概念在 Angular 中文社区中却只有零星几篇相关介绍的文章,另一方面是看到了虽然国内 Angular 开发者虽然数量远少于 React 和 Vue,却依然非常热情的贡献自己的知识和见解来为 Angular 中文社区添砖加瓦,作为已使用 Angular 半年多的开发者,深深感受到 Google 的工程美学。

大而全且不失优雅,是笔者对 Angular 这款 Web 框架的最大感受,感谢开源社区中的各位开发者们~

对于文中描述错误的地方,还望大佬们批评斧正~

原文地址:https://juejin.cn/post/6844904079878012935作者:LangWalker

更多编程相关知识,请访问:编程入门!!

以上就是带你详细了解Angular中的变更检测的详细内容,更多请关注【创想鸟】其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至253000106@qq.com举报,一经查实,本站将立刻删除。

发布者:PHP中文网,转转请注明出处:https://www.chuangxiangniao.com/p/2708974.html

(0)
上一篇 2025年3月7日 20:27:31
下一篇 2025年3月7日 09:00:22

AD推荐 黄金广告位招租... 更多推荐

相关推荐

  • Angular怎么构建组件?3种方法介绍

    angular怎么构建组件?本篇文章给大家介绍一下angular创建项目的方法,angular创建组件的三种方式。 一、angular 与angularjs的区别是什么? 命名变化,Angular2 以后官方命名为Angular, 2.0 …

    2025年3月7日 编程技术
    200
  • 浅谈Angular组件之间通信的5种方法

    angular组件间怎么通信?下面本篇文章给大家介绍一下angular组件之间通信的5种方法,有需要的可以参考~ 组件是angular的构建单元,在项目中为了保证组件之间的数据能够来回的传递,angular封装了一些能够实现组件之间通信的方…

    2025年3月7日
    200
  • 手把手教你使用Angular CDK Portal创建动态内容

    之前介绍过原生 API 的动态视图插入,功能上已经可以满足大多数使用场景。不过也有一些缺憾,还没有解决在 Angular 应用外插入内容的需求,指令也不能跟动态插入的组件有输入输出的交互。 好在 Angular 官方提供了一套组件开发套件 …

    2025年3月7日
    200
  • 浅谈Angular中如何使用路由?

    angular中如何使用路由?本篇文章就来带大家了解一下angular中使用路由的方法,快速入门angular路由,希望对大家有所帮助! 路由的概念在前端的框架中得到了广泛的应用,对于路由的感念不做阐述,路由的应用无外乎就是嵌套、传参,高级…

    2025年3月7日
    200
  • 聊聊Angular中的自定义管道

    angular中管道有什么用?特点是什么?怎么自定义管道?下面本篇文章带大家了解一下angular中的管道,介绍一下自定义管道的方法,希望对大家有所帮助! 一、管道的作用 方便我们在模板中对我们的数据进行格式化处理。【相关教程推荐:《ang…

    2025年3月7日
    200
  • 深入了解Angular中的路由

    什么是路由?本篇文章带大家深入了解一下angular中的路由,希望对大家有所帮助! 路由简介 路由是实现单页面应用的一种方式,通过监听hash或者history的变化,渲染不同的组件,起到局部更新的作用,避免每次URL变化都向服务器请求数据…

    2025年3月7日
    200
  • 深入浅析Angular中的依赖注入

    什么是依赖注入?本篇文章带大家了解一下angular中的依赖注入,希望对大家有所帮助! 依赖注入概念: 维基百科对依赖注入的解释:在软件工程中,依赖注入是实现控制反转的一种软件设计模式,一个依赖是一个被其他对象(client)调用的对象(服…

    2025年3月7日
    200
  • 聊聊Angular中常用的错误处理方式

    本篇文章带大家深入了解一下angular中常用的错误处理方式,希望对大家有所帮助! 错误处理是编写代码经常遇见的并且必须处理的需求,很多时候处理异常的逻辑是为了避免程序的崩溃,本文将简单介绍Angular处理异常的方式。【相关教程推荐:《a…

    2025年3月7日
    200
  • 带你了解Angular10中的双向绑定

    下面本篇文章带大家了解一下双向绑定,看看angular中两种类型的双向绑定,希望对大家有所帮助! 前面我们了解了属性绑定、事件绑定以及输入和输出的使用,是时候了解双向绑定了。本节,我们将利用@Input()和@Output()来了解下双向绑…

    2025年3月7日 编程技术
    200
  • 浅析Angular中什么是ngModule

    什么是ngmodule?本篇文章带给大家简单了解一下angular语法,介绍一下angular中的ngmodule,希望对大家有所帮助! 作为Angular10教程,在我的理解中,angular相较于VUE,它的模块化做得更好,这样使代码结…

    2025年3月7日
    200

发表回复

登录后才能评论