Cet article vous amènera à en savoir plus sur la détection des changements dans Angular et vous présentera le mécanisme de mise à jour du DOM d'Angular, les problèmes que la détection des changements peut résoudre, etc.
À travers cet article, vous pouvez vous aider à acquérir ces connaissances :
Regardons d'abord l'un des. la démo Simply la plus populaire
Lorsque l'on clique sur le bouton, on change l'attribut name du composant, et la valeur modifiée est également affichée dans le DOM en un instant, ce qui semble un peu "magique".
Si l'innterText dans le vrai DOM est imprimé immédiatement après l'instruction de changement d'élément, mais qu'il s'avère qu'il s'agit toujours de l'ancienne valeur, mais que la valeur dans la vue a évidemment changé, que s'est-il passé exactement dans ces deux pièces de code ? Si vous vous posez également cette question, rejoignez-moi pour vous révéler la réponse.
Rappelons soigneusement ce qui vient de se passer :
Cliquez sur le bouton
La valeur change
Si vous utilisez JS natif pour écrire ce code, alors la vue après avoir cliqué sur le bouton ne changera certainement pas, et Dans Angular, la vue change. Si vous avez une compréhension légèrement approfondie d'Angular, vous connaîtrez une bibliothèque appelée zone.js. Si vous regardez attentivement, vous constaterez que zone.js effectue une couche de traitement pour tous les événements susceptibles de modifier les valeurs, tels que. :
Angular nous fournit également une méthode pour désactiver zone.js .
Après avoir désactivé la zone, lorsque nous cliquons à nouveau sur le bouton, la vue n'est pas mise à jour.
Avec curiosité, nous avons trouvé la code clé de mise à jour de la vue dans le code source angulaire
Cette fois, nous avons appelé manuellement cette méthode dans le code.
Exactement comme prévu ! La vue est mise à jour, et ce qui est encore plus surprenant, c'est que le innerText imprimé est également mis à jour !
À ce stade, nous sommes arrivés à la conclusion que La mise à jour du DOM repose sur le déclenchement de tick(), et zone.js aide les développeurs à éviter le déclenchement manuel.
Bon, après un petit test, regardons de plus près ce qui se passe derrière la mise à jour de la vue angulaire.
Regardons d'abord une telle erreur. La valeur du nom du composant parent est modifiée dans le ngOnInit du. En conséquence, un message d'erreur que tout le monde a dû rencontrer est apparu
Mais écrire de cette façon n'entraîne pas toujours une erreur. Par exemple, si nous supprimons l'attribut d'entrée du composant enfant, actualisez-le et constatez que le même code peut s'exécuter, le nom du composant parent peut être modifié normalement.
emmm... Perdu dans mes pensées...
Peut-être que vous êtes comme moi quand j'ai commencé à apprendre Angular, à chercher ce problème dans stackoverflow, à copier un code dont je ne sais pas pourquoi il fonctionne, et puis je l'ai simplement collé, et quand je rencontre à nouveau ce problème, je continue à rechercher et copier-coller dans stackoverflow, et ainsi de suite...
Au fil du temps, vous qui maîtrisez divers CRUD êtes de plus en plus insatisfaits de ce type de programmation orientée stackoverflow. Vous commencez à continuer à chercher des réponses aux questions dans les communautés, les documents et les forums, mais après avoir lu leurs réponses et From the. article, il semble que je sache seulement qu'il existe quelque chose appelé Détection de changement, mais je ne peux pas expliquer exactement ce qui a causé ce bug. Si vous avez la même expérience que moi, alors... Continuez à lire pour découvrir la vérité. !
Lorsque nous modifions les données dans le modèle, la couche framework doit savoir :
Tout le monde doit être familier avec le Virtual Dom dans React. React compare le DOM. Le nouvel état et l'ancien état sont utilisés pour déterminer quelle partie du dom mettre à jour, au lieu de mettre à jour tout le dom. C'est également la même chose que la détection de changement dans Angular.
L'ensemble de l'application Angular est une arborescence de composants. Il est impossible qu'un changement dans un composant déclenche la mise à jour de tous les composants. Par exemple, si l'utilisateur change l'état d'un bouton, alors l'approche la plus idéale consiste simplement à mettre à jour le style ou le texte de ce bouton au lieu de mettre à jour l'ensemble de l'application. C'est le but de la détection des modifications.
Par défaut (ChangeDetectionStrategy.Default
), lorsque la détection de changement du composant parent se produit, le composant enfant déclenchera également la détection de changement. ChangeDetectionStrategy.Default
),父组件的变更检测发生时,子组件也会触发变更检测。
(CD
即为 changeDetection
)
每次变更检测时,都会比较新旧状态,如果两次变更检测(开发环境下)的结果不一致就会报错,例如:
Expression has changed after it was checked
这也就解释了为什么在子组件中更改了父组件的值会报错。
但是!在前面的两个例子中我们都在子组件中更改了父组件的值,只有第一个报错,第二个是可以正常更新的,如果你也同样很疑惑这中间真正的差异点在哪里,那么接着往下阅读吧~
先上结论:
更新所有子组件的绑定属性
调用所有子组件的 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 源码当中也有描述。
当然你不能因为这个 bug 就强制在开发环境下使用生产模式...
ChangeDetectionStrategy
默认为 Default,也就是父组件的 CD 会触发子组件的 CD,但是很显然有些情况下我们可以自行判断出某些子组件在父组件 CD 时并不用触发,而 OnPush
CD
est changeDetection
)🎜🎜Chaque fois qu'un changement est détecté, l'ancien et le nouvel état seront comparés si les résultats des deux détections de changement (dans l'environnement de développement) sont incohérents, une erreur sera signalée, par exemple :🎜🎜L'expression a changé après avoir été vérifiée
🎜🎜Cela explique également pourquoi une erreur est signalée lorsque la valeur du composant parent est modifiée dans l'enfant composant. 🎜🎜Mais ! Dans les deux exemples précédents, nous avons modifié la valeur du composant parent dans le composant enfant. Seul le premier a signalé une erreur, et le second a pu être mis à jour normalement. Si vous ne comprenez pas non plus la différence réelle entre eux, lisez ensuite. on~🎜export declare abstract class ChangeDetectorRef { abstract checkNoChanges(): void; abstract detach(): void; abstract detectChanges(): void; abstract markForCheck(): void; abstract reattach(): void; }
enableProdMode()< sera appelé en production /code>, le nombre de détections de changements passera de 2 à 1, cette partie est dans <a href="https://github.com/angular/angular/blob/6b79ab5abec8b5a4b43d563ce65f032990b3e3bc/packages/core/src/application_ref. .ts#L553" target="_blank" ref="nofollow noopener noreferrer">Le code source angulaire</a> est également décrit. 🎜🎜<img src="https://img.php.cn/upload/image/766/561/889/162864959954984Vous amène à en savoir plus sur la détection des changements dans Angular" title="162864959954984Vous amène à en savoir plus sur la détection des changements dans Angular" alt="Vous amène à en savoir plus sur la détection des changements dans Angular"/>🎜🎜De Bien sûr, vous ne pouvez pas forcer l'utilisation du mode production dans l'environnement de développement simplement à cause de ce bug...🎜<h3 data-id="heading-5">🎜4. Qu'est-ce que ChangeDetectionStrategy.OnPush dont tout le monde parle souvent ? 🎜🎜🎜<code>ChangeDetectionStrategy
est par défaut sur Default, c'est-à-dire que le CD du composant parent déclenchera le CD du composant enfant, mais évidemment dans certains cas, nous pouvons juger par nous-mêmes que certains composants enfants ne sont pas utilisés lorsque le composant parent CD est déclenché, tandis que OnPush
🎜则是 Angular 为开发者提供的一便捷操作方式。
用动图来表示就是:查看链接
知名的 Angular 开源组件库 ng-zorro 就使用了大量的 OnPush 策略,这也是 Angular 性能优化的方法之一。
Angular 给每个组件都关联了一份组件视图,通过 ChangeDetectorRef
可以拿到相关联的视图,在定义中我们可以看到:
export declare abstract class ChangeDetectorRef { abstract checkNoChanges(): void; abstract detach(): void; abstract detectChanges(): void; abstract markForCheck(): void; abstract reattach(): void; }
观察下面的动图,被 detached
的组件将不会被检查变更。
而 reattach
则可以让被 detached
的组件重新可以被检测到变更。
reattach
只会重新启用对当前组件的变更检测,但是如果父组件没有启动变更检测,那么 reattach
并不会起作用,而 markForCheck
可以很好地解决这个问题。
这一点在 ng-zorro 的源码中可以了解一二。
例如在 nz-anchor 组件中更改 nz-anchor-link 组件的 active 属性时,由于本身 ChangeDetectionStrategy
为 OnPush
,那么就需要激活 markForCheck 来重新启用检测。具体写法可以查看 github 中的源代码。
用动图来展示则是这样,注意观察设置了 MFC 的前后变化
这个方法如同字面意思一样很好理解,就是触发一次变更检测啦,还记得本文中的第一个例子吗,我们不手动触发 tick()
,而是触发 detechtChanges()
也是可以达到效果的。
到这里,我相信大家已经基本弄明白了 Angular 变更检测,如果有任何疑问,欢迎在评论区交流讨论~
在撰写这篇文章时,笔者参(fu)考(zhi)了大量的社区文章和讨论,一方面是感慨如此重要的概念在 Angular 中文社区中却只有零星几篇相关介绍的文章,另一方面是看到了虽然国内 Angular 开发者虽然数量远少于 React 和 Vue,却依然非常热情的贡献自己的知识和见解来为 Angular 中文社区添砖加瓦,作为已使用 Angular 半年多的开发者,深深感受到 Google 的工程美学。
大而全且不失优雅,是笔者对 Angular 这款 Web 框架的最大感受,感谢开源社区中的各位开发者们~
对于文中描述错误的地方,还望大佬们批评斧正~
原文地址:https://juejin.cn/post/6844904079878012935
作者:LangWalker
更多编程相关知识,请访问:编程入门!!
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!