Maison  >  Article  >  interface Web  >  Vous amène à en savoir plus sur la détection des changements dans Angular

Vous amène à en savoir plus sur la détection des changements dans Angular

青灯夜游
青灯夜游avant
2021-08-11 10:48:562338parcourir

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.

Vous amène à en savoir plus sur la détection des changements dans Angular

À travers cet article, vous pouvez vous aider à acquérir ces connaissances :

  • Comment se fait la mise à jour du DOM d'Angular ? [Recommandation de didacticiel connexe : "tutoriel angulaire"]
  • Quels problèmes la détection des changements résout-elle ?
  • Comprenez plus en profondeur la détection des changements, familiarisez-vous avec sa définition dans le code source Angular et son utilisation dans ng-zorro
  • Et quelques idées personnelles

1 Le mécanisme de mise à jour DOM d'Angular

Regardons d'abord l'un des. la démo Simply la plus populaire

Vous amène à en savoir plus sur la détection des changements dans Angular

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".

Vous amène à en savoir plus sur la détection des changements dans Angular

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. :

  • Événements : clic, survol, mouseout, keyup, keydown et autres événements du navigateur
  • Timer : setTimeout, setInterval
  • XHR : diverses requêtes, etc.

Angular nous fournit également une méthode pour désactiver zone.js .

Vous amène à en savoir plus sur la détection des changements dans Angular

Vous amène à en savoir plus sur la détection des changements dans Angular

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

Vous amène à en savoir plus sur la détection des changements dans Angular

Cette fois, nous avons appelé manuellement cette méthode dans le code.

Vous amène à en savoir plus sur la détection des changements dans Angular

7-Vous amène à en savoir plus sur la détection des changements dans Angular

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.

2. Découvrez les secrets de la détection des changements

1. Commençons par une erreur courante

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

Vous amène à en savoir plus sur la détection des changements dans Angular

Vous amène à en savoir plus sur la détection des changements dans Angular

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.

Vous amène à en savoir plus sur la détection des changements dans Angular

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é. !

2. Quelle est la détection de changement dont je parle depuis longtemps ?

Lorsque nous modifions les données dans le modèle, la couche framework doit savoir :

  • Où le modèle a changé
  • Où la vue doit être mise à jour

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

Vous amène à en savoir plus sur la détection des changements dans 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 = &#39;child&#39;

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

Vous amène à en savoir plus sur la détection des changements dans Angular

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

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

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

Vous amène à en savoir plus sur la détection des changements dans Angular🎜🎜( 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~🎜

🎜3. La clé du problème - Séquence de détection🎜🎜🎜Tout d'abord, concluons : 🎜
    🎜🎜Mettre à jour les propriétés de liaison de tous les sous-composants🎜🎜🎜🎜Appeler les hooks de cycle de vie OnChanges, OnInit, DoCheck, AfterContentInit de tous les sous-composants🎜🎜🎜🎜Mettre à jour le DOM du composant actuel🎜🎜🎜🎜Déclencher des modifications pour les sous-composants Détecter 🎜🎜 🎜🎜Appelez le hook du cycle de vie AfterViewInit de tous les sous-composants🎜🎜🎜🎜Nous ne nous concentrons pas sur les détails trop fins ici (ne vous demandez pas pourquoi c'est dans cet ordre, rappelez-vous simplement que c'est ainsi qu'il est configuré dans Angular) Oui, si quelqu'un souhaite parler des idées de conception d'Angular dans cette partie, veuillez laisser un message dans la zone de commentaires pour en discuter ~) 🎜🎜Dans le premier exemple, le composant parent parent transmet le nom de l'attribut d'entrée à l'enfant composant enfant, et le composant enfant L'attribut name du composant parent est mis à jour dans ngOnInit, ce qui signifie que ce code viole la séquence de détection (la première étape est opérée dans la deuxième étape de la séquence) ! 🎜
    export declare abstract class ChangeDetectorRef {
        abstract checkNoChanges(): void;
        abstract detach(): void;
        abstract detectChanges(): void;
        abstract markForCheck(): void;
        abstract reattach(): void;
    }
    🎜Dans le deuxième exemple, même si le composant enfant met également à jour l'attribut name du composant parent dans ngOnInit, car le composant parent parent ne lie pas le nom de l'attribut d'entrée au composant enfant enfant, il n'y aura pas de violation de détection de changement . ordre de file d'attente, afin qu'il puisse fonctionner normalement. 🎜rrreee🎜En ce moment, consultez les réponses très appréciées sur stackoverflow N'est-ce pas beaucoup plus clair ? D'après la séquence de détection ci-dessus, nous constaterons que tant que le composant parent a une liaison de propriété sur le composant enfant, peu importe qu'il se trouve dans l'un des hooks du cycle de vie parmi OnChanges, OnInit, DoCheck, AfterContentInit et AfterViewInit L'exécution du code suivant entraînera une erreur. 🎜rrreee🎜D'accord, nous avons maintenant compris la vraie raison pour laquelle cette erreur se produit, mais je tiens quand même à vous rappeler que cette erreur ne sera déclenchée que dans l'environnement de développement, et enableProdMode(), 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.%20.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%20am%C3%A8ne%20%C3%A0%20en%20savoir%20plus%20sur%20la%20d%C3%A9tection%20des%20changements%20dans%20Angular" 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 为开发者提供的一便捷操作方式。

1Vous amène à en savoir plus sur la détection des changements dans Angular

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

1Vous amène à en savoir plus sur la détection des changements dans Angular

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

1Vous amène à en savoir plus sur la détection des changements dans 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 的组件将不会被检查变更。

Vous amène à en savoir plus sur la détection des changements dans Angular

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

2.markForCheck

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

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

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

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

Vous amène à en savoir plus sur la détection des changements dans Angular

3.detectChanges

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

1Vous amène à en savoir plus sur la détection des changements dans Angular

Vous amène à en savoir plus sur la détection des changements dans Angular

四、最后

到这里,我相信大家已经基本弄明白了 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!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer