Gérer les intercepteurs dans React

Bonjour, Habr!

Avec une fierté et un soulagement incroyables, nous avons remis ce soir un nouveau livre sur React à l'imprimerie.



A cette occasion, nous vous proposons une traduction légèrement abrégée d'un article de Dan Abramov, décrivant l'utilisation des intercepteurs dans la 16e version de React. Le livre, que nous attendons déjà avec impatience, est décrit au chapitre 5.

La semaine dernière, Sophie Alpert et moi avons présenté le concept des «intercepteurs» lors de la conférence React Conf, suivie d'une discussion détaillée sur le sujet par Ryan Florence .

Je vous recommande fortement de regarder cette conférence plénière pour vous familiariser avec l'éventail des problèmes que nous essayons de résoudre avec l'aide d'intercepteurs. Cependant, même l'heure de votre temps que j'apprécie beaucoup, j'ai donc décidé de décrire brièvement dans cet article les principales considérations pour les intercepteurs.
Remarque: les intercepteurs React sont encore expérimentaux. Pas besoin de s'y plonger tout de suite. Notez également que cette publication expose mes opinions personnelles, qui peuvent ne pas coïncider avec la position des développeurs de React.
Pourquoi faut-il des intercepteurs?

Il est connu que l'organisation des composants et un flux descendant de données aident à organiser une grande interface utilisateur sous la forme de petits fragments indépendants et réutilisables. Cependant, il n'est souvent pas possible de décomposer des composants complexes au-delà d'une certaine limite, car la logique préserve l'état et n'est pas extractible dans une fonction ou un autre composant . Parfois, ceux qui disent que React ne parvient pas à une «séparation des tâches» s'en plaignent.
De tels cas sont très courants et sont associés, par exemple, à l'animation, au traitement des formulaires, à la connexion à des sources de données externes et à de nombreuses autres opérations que nous pourrions avoir besoin d'effectuer avec nos composants. En essayant de résoudre ces problèmes avec des composants seuls, nous obtenons généralement:

  • Composants géants difficiles à refactoriser et à tester.
  • Duplication de la logique entre divers composants et méthodes de cycle de vie.
  • Modèles complexes , en particulier, accessoires de rendu et composants d'ordre supérieur.

Nous pensons que les intercepteurs sont les plus prometteurs pour résoudre tous ces problèmes. Les intercepteurs aident à organiser la logique à l'intérieur du composant sous la forme d'unités isolées réutilisables :





Les intercepteurs sont conformes à la philosophie React (flux de données explicite et composition) et au sein d'un composant, et pas seulement entre les composants . C'est pourquoi il me semble que les intercepteurs s'intègrent naturellement dans le modèle de composant React.

Contrairement aux modèles tels que les propriétés de rendu ou les composants d'ordre supérieur, les renifleurs ne surchargent pas votre arborescence de composants avec des pièces jointes inutilement profondes. De plus, ils ne présentent pas les inconvénients inhérents aux impuretés.

Même si à première vue les intercepteurs vous déforment (tout comme moi au début!) Je recommande de donner une chance à cette option et de l'expérimenter. Je pense que vous l'aimerez.

Le gonflement de React est-il dû aux intercepteurs?

Jusqu'à ce que nous ayons couvert les intercepteurs en détail, vous pouvez craindre que l'ajout d'intercepteurs dans React ne soit qu'une multiplication d'entités. C'est une critique juste. Je pense ceci: bien qu'à court terme, vous ressentiez vraiment une charge cognitive supplémentaire (pour les étudier), au final vous vous sentirez mieux.

Si les intercepteurs prennent racine dans la communauté React, alors en fait le nombre d'entités à gérer lors de l'écriture d'applications React sera réduit . À l'aide d'intercepteurs, vous pouvez constamment utiliser des fonctions, plutôt que de basculer entre les fonctions, les classes, les composants d'ordre supérieur et le rendu des composants.

Quant à l'augmentation de la taille de l'implémentation, l'application React avec le support des intercepteurs n'augmente que d'environ ~ 1,5 Ko (min + gzip). Bien que cela ne soit pas trop en soi, il est très probable que lorsque vous utilisez des intercepteurs, la taille de votre assembly diminue même , car le code d'intercepteur est généralement mieux minifié qu'un code équivalent utilisant des classes. L'exemple suivant est légèrement extrême, mais il montre clairement pourquoi tout est ainsi ( cliquez pour développer le fil entier):



Il n'y a aucun changement révolutionnaire à la proposition d'intercepteur . Votre code fonctionnera correctement même si vous commencez à utiliser des intercepteurs dans de nouveaux composants. En fait, c'est exactement ce que nous recommandons: ne réécrivez rien au niveau mondial! Il serait sage d'attendre que l'utilisation d'intercepteurs soit établie dans tout le code critique. Néanmoins, nous serons reconnaissants si vous pouvez expérimenter avec la version alpha 16.7 et nous laisser des commentaires sur la proposition d'intercepteurs , ainsi que signaler tout bogue .

Qu'est-ce que c'est - les intercepteurs?

Pour comprendre ce que sont les intercepteurs, vous devez revenir en arrière et réfléchir à la réutilisation du code.

Il existe de nombreuses façons de réutiliser la logique dans les applications React aujourd'hui. Donc, pour calculer quelque chose, vous pouvez écrire des fonctions simples, puis les appeler. Vous pouvez également écrire des composants (qui peuvent eux-mêmes être des fonctions ou des classes). Les composants sont plus puissants, mais lorsque vous travaillez avec eux, vous devez afficher une interface utilisateur. Par conséquent, l'utilisation de composants n'est pas pratique pour transmettre une logique non visuelle. Nous arrivons donc à des motifs complexes comme les propriétés de rendu et les composants d'ordre supérieur. React ne faciliterait-il pas la tâche s'il n'y avait qu'une seule façon générale de réutiliser le code, et pas tant?

Les fonctions semblent être parfaites pour le code réutilisable. Passer la logique entre les fonctions est le moins cher. Cependant, l'état local de React ne peut pas être stocké dans les fonctions. Vous ne pouvez pas extraire un comportement comme «suivre la taille de la fenêtre et l'état de mise à jour» ou «animer une valeur pendant un certain temps» à partir d'un composant de classe sans restructurer le code ou sans introduire d'abstractions telles que Observables. Les deux approches ne font que compliquer le code, et React est gentil avec notre simplicité.

Les intercepteurs résolvent ce problème. Grâce aux intercepteurs, vous pouvez utiliser les fonctionnalités React (par exemple, state) à partir d'une fonction - en l'appelant une seule fois. React fournit plusieurs intercepteurs intégrés qui correspondent aux briques React: état, cycle de vie et contexte.

Étant donné que les intercepteurs sont des fonctions JavaScript normales, vous pouvez combiner les intercepteurs intégrés fournis dans React pour créer des «intercepteurs natifs» . Ainsi, des problèmes complexes peuvent être résolus avec une seule ligne de code, puis le multiplier dans votre application ou le partager dans la communauté React

Attention: à proprement parler, vos propres intercepteurs ne font pas partie des fonctionnalités de React. La capacité d'écrire vos propres intercepteurs découle naturellement de leur organisation très interne.

Montrez-moi le code!

Supposons que nous voulons abonner un composant à la largeur de la fenêtre actuelle (par exemple, pour afficher un autre contenu ou une zone de visualisation plus étroite).
Un code similaire peut être écrit aujourd'hui de plusieurs manières. Par exemple, pour créer une classe, créer plusieurs méthodes de cycle de vie, ou peut-être même recourir à des propriétés de rendu ou appliquer un composant d'ordre supérieur si vous recherchez une réutilisation. Cependant, je pense que rien ne se compare à cela:



gist.github.com/gaearon/cb5add26336003ed8c0004c4ba820eae

Si vous lisez ce code, cela signifie qu'il fait exactement ce qu'il dit . Nous utilisons la largeur de la fenêtre à l'intérieur de notre composant et React redessine votre composant s'il change. C'est précisément pour cela que les intercepteurs sont nécessaires - pour rendre les composants vraiment déclaratifs, même s'ils contiennent des états et des effets secondaires.

Considérez comment ce propre intercepteur pourrait être implémenté. Nous pourrions utiliser l'état React local pour y conserver la largeur actuelle de la fenêtre et définir l'état lorsque la fenêtre est redimensionnée à l'aide d'un effet secondaire:



gist.github.com/gaearon/cb5add26336003ed8c0004c4ba820eae

Comme illustré ci-dessus, les intercepteurs useState comme useState et useEffect servent de briques. Nous pouvons les utiliser directement à partir de nos composants, ou assembler nos propres intercepteurs à partir d'eux, par exemple, useWindowWidth . Utiliser vos propres intercepteurs ne semble pas moins idiomatique que de travailler avec l'API React intégrée.

En savoir plus sur les intercepteurs intégrés dans cette revue .

Les intercepteurs sont encapsulés - chaque fois que l'intercepteur est appelé, il reçoit un état local isolé à l'intérieur du composant en cours d'exécution . Dans cet exemple particulier, ce n'est pas important (la largeur de la fenêtre est la même pour tous les composants!), Mais c'est justement là que réside la puissance des intercepteurs! Ils sont destinés à séparer non pas la logique, mais la conservation de l'état. Nous ne voulons pas interrompre le flux de données en aval!

Chaque intercepteur peut contenir certains états locaux et effets secondaires. Vous pouvez transférer des données entre plusieurs intercepteurs, comme cela se fait habituellement entre les fonctions. Ils peuvent prendre des arguments et renvoyer des valeurs car ce sont des fonctions JavaScript.

Voici un exemple de bibliothèque d'animation React où nous expérimentons avec des intercepteurs:
Remarquez comment une animation époustouflante est implémentée dans le code source illustré: nous transmettons des valeurs entre plusieurs intercepteurs natifs au sein de la même fonction de rendu.



codesandbox.io/s/ppxnl191zx

(Cet exemple est traité plus en détail dans ce guide .)

En raison de la possibilité de transférer des données entre les intercepteurs, ils sont très pratiques pour implémenter des animations, s'abonner aux données, gérer des formulaires et travailler avec d'autres abstractions avec état. Contrairement aux propriétés de rendu ou aux composants d'ordre supérieur, les intercepteurs ne créent pas de «fausse hiérarchie» dans votre arborescence de rendu . Ils ressemblent plus à une liste bidimensionnelle de "cellules de mémoire" attachées à un composant. Pas de niveaux supplémentaires.

Et les cours?

À notre avis, nos propres intercepteurs sont le détail le plus intéressant de toute l'offre. Mais pour que ses propres intercepteurs soient fonctionnels, React doit fournir au niveau des fonctions la capacité de déclarer l'état et les effets secondaires. C'est exactement ce qui nous permet de faire des intercepteurs useState comme useState et useEffect . En savoir plus à ce sujet dans la documentation .

Il s'avère que de tels intercepteurs intégrés sont pratiques non seulement lors de la création de vos propres intercepteurs. Ils sont également suffisants pour déterminer les composants dans leur ensemble, car ils nous fournissent les capacités nécessaires - par exemple, l'état. C'est pourquoi nous souhaitons que les intercepteurs deviennent le principal moyen de définir les composants React à l'avenir.
Non, nous ne prévoyons pas d'abolir progressivement les cours. Nous utilisons des dizaines de milliers de composants de classe sur Facebook et nous (tout comme vous) ne voulons absolument pas les réécrire. Mais, si la communauté React commence à utiliser des intercepteurs, il deviendra inapproprié de conserver les deux façons recommandées d'écrire des composants. Les intercepteurs couvrent tous les cas pratiques dans lesquels les classes sont utilisées, mais offrent une plus grande flexibilité lors de l'extraction, du test et de la réutilisation du code. C'est pourquoi nous connectons les intercepteurs à nos idées sur l'avenir de React.

Et si les intercepteurs sont magiques?

Peut - être que les règles d'interception vous dérouteront.

Bien qu'il ne soit pas habituel d'appeler un intercepteur au niveau supérieur, vous ne voudriez probablement pas déterminer vous-même la condition dans la condition, même si vous le pouviez . Par exemple, un état lié à une condition ne peut pas être déterminé en classe, et pendant quatre ans de communication avec les utilisateurs de React, je n'ai entendu aucune plainte à ce sujet.

Une telle conception est essentielle pour introduire vos propres intercepteurs sans introduire de bruit de syntaxe excessif ou créer des pièges. Nous comprenons que par habitude, c'est difficile, mais nous pensons que ce compromis est acceptable, compte tenu des opportunités qu'il offre. Si vous n'êtes pas d'accord, je vous suggère de vous expérimenter et d'essayer comment vous aimez cette approche.

Nous utilisons des hooks de production depuis un mois maintenant pour voir si les nouvelles règles vont dérouter les programmeurs. La pratique montre qu'une personne maîtrise les intercepteurs en quelques heures. J'avoue que ces règles me semblaient à première vue une hérésie, mais ce sentiment s'est rapidement dissipé. C'est l'impression que j'ai eue lors de ma première rencontre avec React. (Vous n'aimiez pas React? Et je ne l'ai aimé que pour la deuxième fois.)

Attention: au niveau de l'implémentation des intercepteurs, il n'y a pas de magie non plus. Selon Jamie , elle obtient quelque chose comme ça:



gist.github.com/gaearon/62866046e396f4de9b4827eae861ff19

Nous maintenons une liste éclatée d'intercepteurs et passons au composant suivant de la liste chaque fois que vous utilisez un intercepteur. Grâce aux règles des intercepteurs, leur ordre est le même dans n'importe quel moteur de rendu, donc à chaque appel, nous pouvons fournir au composant l'état correct.

( Dans cet article de Rudy Yardley, tout est magnifiquement expliqué sur les photos!)

Vous vous êtes peut-être demandé où React stocke l'état des intercepteurs. Au même endroit que l'état des cours. React dispose d'une file d'attente de mise à jour interne qui contient la vérité ultime pour chaque état, quelle que soit la façon dont vous définissez vos composants.

Les intercepteurs sont indépendants des proxys et des getters, qui sont si courants dans les bibliothèques JavaScript modernes. Par conséquent, on peut affirmer qu'il y a moins de magie dans les intercepteurs que dans d'autres approches populaires pour résoudre de tels problèmes. Pas plus que dans array.push et array.pop (dans le cas où l'ordre des appels est également important!)

La conception de l'intercepteur n'est pas liée à React. En fait, quelques jours après la publication de la proposition, diverses personnes nous ont montré des implémentations expérimentales de la même API d'intercepteur pour Vue, des composants Web et même des fonctions JavaScript ordinaires.
Enfin, si vous êtes fanatiquement dévoué à la programmation fonctionnelle et que vous n'êtes pas à l'aise lorsque React commence à s'appuyer sur un état modifiable dans le cadre d'une implémentation. Mais, cela peut vous rassurer que le traitement des intercepteurs peut être implémenté dans sa forme pure, se limitant aux effets algébriques (s'ils étaient pris en charge en JavaScript). Naturellement, au niveau intra-système, React s'est toujours appuyé sur un état mutable - et c'est exactement ce que vous souhaitez éviter.

Quel que soit le point de vue le plus proche de vous - pragmatique ou dogmatique - j'espère qu'au moins une de ces options vous semble logique. Plus important encore, à mon avis, les intercepteurs simplifient notre travail et il devient plus pratique pour les utilisateurs de travailler. C'est ce que les intercepteurs me soudoient comme ça.

Source: https://habr.com/ru/post/fr428632/


All Articles