Composants de Final React

Ce que j'aime dans l'écosystème React, c'est que IDEA est à l'origine de nombreuses décisions. Divers auteurs écrivent divers articles à l'appui de l'ordonnance existante et expliquent pourquoi tout est «bien», afin que tout le monde comprenne que le parti est sur la bonne voie.


Après un certain temps, l'IDEA change un peu et tout recommence depuis le début.


Et le début de cette histoire est la séparation des composants en conteneurs et non-conteneurs (communément appelés composants muets, désolé pour mon français).



Le problème


Le problème est très simple - les tests unitaires. Récemment, il y a eu un certain mouvement vers les tests d'intégration - eh bien, vous savez "Ecrire des tests. Pas trop. Surtout l'intégration." . Ce n'est pas une mauvaise idée, et si le temps est court (et que les tests ne sont pas particulièrement nécessaires) - c'est ce que vous devez faire. Appelons cela des tests de fumée - pour vérifier que rien ne semble exploser.


S'il y a beaucoup de temps et que des tests sont nécessaires, il vaut mieux ne pas suivre cette voie, car écrire de bons tests d'intégration est très, très long. Tout simplement parce qu'ils vont grandir et grandir, et pour tester le troisième bouton à droite, vous devrez d'abord cliquer sur 3 boutons dans le menu, et n'oubliez pas de vous connecter. En général - voici une explosion combinatoire sur un plateau d'argent.


La solution ici est un et simple (par définition) - tests unitaires. La possibilité de démarrer des tests avec un état prêt à l'emploi d'une partie de l'application. Plus précisément, pour réduire (rétrécir) la zone de test de l'Application ou du Big Block à quelque chose de petit - une unité, quelle qu'elle soit. Il n'est pas nécessaire d'utiliser une enzyme - vous pouvez exécuter des tests de navigateur, si l'âme le demande. La chose la plus importante ici est de pouvoir tester quelque chose isolément . Et sans trop de peine.


L'isolement est l'un des points clés des tests unitaires, et c'est pourquoi les tests unitaires n'aiment pas. Ils ne l'aiment pas pour diverses raisons:


  • par exemple, votre «unité» est arrachée à l'application et ne fonctionne pas dans sa composition même lorsque ses propres tests sont verts.
  • ou par exemple parce que l'isolement est un cheval si sphérique dans le vide que personne n'a vu. Comment y parvenir et comment le mesurer?

Personnellement, je ne vois aucun problème ici. Dans le premier paragraphe, bien sûr, vous pouvez recommander des tests d'intégration, ils ont été inventés pour cela - pour vérifier comment les composants pré-testés sont assemblés correctement. Vous faites confiance aux packages npm qui testent, bien sûr, uniquement eux-mêmes et non eux-mêmes dans le cadre de votre application. En quoi vos «composants» diffèrent-ils de «pas vos» packages?


Avec le deuxième paragraphe, tout est un peu plus compliqué. Et cet article sera exactement sur ce point (et tout ce qui était auparavant - une introduction) - sur la façon de rendre une unité « testable» .


Diviser et conquérir


L'idée de séparer les composants React en "Container" et "Presentation" n'est pas nouvelle, bien décrite et a déjà réussi à devenir un peu dépassée. Si nous prenons comme base (ce que font 99% des développeurs) un article de Dan Abramov , alors le volet Présentation:


  • Sont concernés par l'apparence des choses
  • Peut contenir à la fois des composants de présentation et de conteneur ** intérieur, et possède généralement un balisage DOM et des styles qui lui sont propres)
  • Emplacements de support (permettent souvent le confinement via this.props.children)
  • Indépendant de l'application (ne dépend pas du reste de l'application, comme les actions de Flux ou les magasins)
  • Ne dépend pas des données (ne spécifiez pas comment les données sont chargées ou mutées)
  • L'interface est basée sur des accessoires (recevoir des données et des rappels exclusivement via des accessoires)
  • Souvent apatrides (ont rarement leur propre état (quand c'est le cas, c'est l'état de l'interface utilisateur plutôt que les données))
  • Souvent SFC (sont écrits en tant que composants fonctionnels, sauf s'ils nécessitent un état, des crochets de cycle de vie ou des optimisations de performances)

Eh bien, les conteneurs sont toute la logique, tous les accès aux données et toute l'application en principe.


Dans un monde idéal, les conteneurs sont le tronc et les composants de présentation sont les feuilles.

Il y a deux points clés dans la définition de Dan: "Application indépendante" , qui est presque une définition académique de "unité", et * "Peut contenir à la fois d'autres composants de présentation et conteneurs ** " *, où ces étoiles sont particulièrement intéressantes.


(traduction gratuite) ** Dans les premières versions de mon article, j'ai (Dan) dit que les composants de présentation ne devraient contenir que d'autres composants de présentation. Je ne pense plus. Le type de composant est les détails et peut changer avec le temps. En général, ne le partagez pas et tout ira bien.

Rappelons-nous ce qui se passe après cela:


  • Dans le livre de contes, tout tombe, car une sorte de conteneur, dans le troisième bouton à gauche, se glisse dans le côté duquel il n'y en a pas. Salutations spéciales à graphql, react-router et autres react-intl.
  • La possibilité d'utiliser mount dans les tests est perdue, car elle rend tout de A à Z, et encore une fois, quelque part dans les profondeurs de l'arbre de rendu, quelqu'un fait quelque chose et les tests tombent.
  • La capacité de contrôler l'état de l'application est perdue, car (au sens figuré), la capacité de mouiller les sélecteurs / résolveurs (en particulier avec proxyquire) est perdue et tout le côté doit être mouillé. Et c'est cool pour les tests unitaires.

Si vous pensez que les problèmes sont un peu farfelus, essayez de travailler en équipe lorsque ces conteneurs, qui seront utilisés dans vos non-conteneurs, changent dans d'autres départements, et en conséquence, vous et eux regardent les tests et vous ne pouvez pas comprendre pourquoi hier tout cela a fonctionné, et maintenant encore.

En conséquence, vous devez utiliser peu profond, ce qui, par conception, élimine tous les effets secondaires nocifs (et inattendus). Voici un exemple simple de l'article «Pourquoi j'utilise toujours peu profond»


Imaginez que l'infobulle affiche "?", Lorsque vous cliquez dessus, le type lui-même sera affiché.


 import Tooltip from 'react-cool-tooltip'; const MyComponent = () => { <Tooltip> hint: {veryImportantTextYouHaveToTest} </Tooltip> } 

Comment le tester? Monter + cliquer + vérifier ce qui est visible. Il s'agit d'un test d'intégration, pas d'une unité, et la question est de savoir comment cliquer sur un composant "étranger" pour vous. Il n'y a pas de problème avec le superficiel, car il n'y a pas de cerveau et la «composante extraterrestre» elle-même. Mais il y a des cerveaux ici, car Tooltip est un conteneur, tandis que MyComponent est pratiquement une présentation.


 jest.mock('react-cool-tooltip', {default: ({children}) => childlren}); 

Mais si vous réagissez-cool-info-bulle, il n'y aura aucun problème avec les tests. La «composante» est devenue nettement plus bête, beaucoup plus courte, beaucoup plus finie .


Composant final


  • un composant avec une taille bien connue, qui peut comprendre d'autres composants finaux précédemment connus, ou ne pas les contenir du tout.
  • ne contient pas d'autres conteneurs, car ils contiennent un état non contrôlé et "augmentent" la taille, c'est-à-dire rendre le composant actuel infini .
  • sinon c'est un composant de présentation régulier. En fait, exactement comme décrit dans la première version de l'article de Dan.

Le dernier élément n'est qu'un engrenage sorti d'un grand mécanisme.


Toute la question est de savoir comment l'enlever.


Solution 1 - DI


Mon préféré est l'injection de dépendance. Dan l'aime aussi . En général, ce n'est pas DI, mais des "slots". En un mot - pas besoin d'utiliser des conteneurs à l'intérieur de la présentation - ils doivent y être injectés . Et dans les tests, il sera possible d'injecter autre chose.


 //    mount     const PageChrome = ({children, aside}) => ( <section> <aside>{aside}</aside> {children} </section> ); //     shallow,       //     mount ? , ,   wiring? const PageChromeContainer = () => ( <PageChrome aside={<ASideContainer />}> <Page /> </PageChrome> ); 

C'est exactement le cas lorsque "les conteneurs sont le tronc et les composants de présentation sont des feuilles"


Solution 2 - Limites


DI peut souvent être cool. Probablement maintenant% username% pense comment il peut être appliqué sur la base de code actuelle, et la solution n'est pas inventée ...


Dans de tels cas, Borders vous sauvera.


 const Boundary = ({children}) => ( process.env.NODE_ENV === 'test' ? null : children // //  jest.mock ); const PageChrome = () => ( <section> <aside><Boundary><ASideContainer /></Boundary></aside> <Boundary><Page /></Boundary> </section> ); 

Ici, au lieu de «fentes», tous les «points de transition» se transforment simplement en limites, ce qui rendra tout pendant les tests. De façon assez déclarative , et exactement ce dont vous avez besoin pour "sortir l'équipement".


Solution 3 - Niveau


Les bordures peuvent être un peu grossières, et il pourrait être plus facile de les rendre un peu plus intelligentes en ajoutant un peu de connaissances sur la couche.


 const checkTier = tier => tier === currentTier; const withTier = tier => WrapperComponent => (props) => ( (process.env.NODE_ENV !== 'test' || checkTier(tier)) && <WrapperComponent{...props} /> ); const PageChrome = () => ( <section> <aside><ASideContainer /></aside> <Page /> </section> ); const ASideContainer = withTier('UI')(...) const Page = withTier('Page')(...) const PageChromeContainer = withTier('UI')(PageChrome); 

Sous le nom Tier / Layer, il peut y avoir différentes choses - fonctionnalité, canard, module, ou simplement cette couche / couche. Le point n'est pas important, l'essentiel est que vous pouvez tirer l'engrenage, peut-être pas un, mais le nombre final, en tirant en quelque sorte une ligne entre ce dont vous avez besoin et ce dont vous n'avez pas besoin (pour différents tests, c'est une frontière différente).


Et rien n'empêche de marquer ces limites différemment.


Solution 4 - Préoccupations distinctes


Si la solution (par définition) réside dans la séparation des entités - que se passera-t-il si nous les prenons et les séparons?


Les «conteneurs», que nous n'aimons pas tellement, sont généralement appelés conteneurs . Et sinon - rien n'empêche dès maintenant de commencer à nommer les composants d'une manière plus sonore. Ou ils ont un certain modèle dans leur nom - Connect (WrappedComonent) ou GraphQL / Query.


Que se passe-t-il si au moment de l'exécution tracer une ligne entre les entités sur la base d'un nom?


 const PageChrome = () => ( <section> <aside><ASideContainer /></aside> <Page /> </section> ); // remove all components matching react-redux pattern reactRemock.mock(/Connect\(\w\)/) // all any other container reactRemock.mock(/Container/) 

Plus une ligne dans les tests, et react-remock supprimera tous les conteneurs susceptibles d'interférer avec les tests.


En principe, cette approche peut être utilisée pour tester les conteneurs eux-mêmes - il vous suffit de supprimer tout sauf le premier conteneur.


 import {createElement, remock} from 'react-remock'; //  "" const ContainerCondition = React.createContext(true); reactRemock.mock(/Connect\(\w\)/, (type, props, children) => ( <ContainerCondition.Consumer> { opened => ( opened ? ( // ""     <ContainerCondition.Provider value={false}> {createElement(type, props, ...children)} <ContainerCondition.Provider> ) // "" : null )} </ContainerCondition.Consumer> ) 

Encore une fois - quelques lignes et équipement enlevés.


Total


Au cours de la dernière année, le test des composants React est devenu plus compliqué, en particulier pour le montage - vous devez écraser les 10 fournisseurs et contextes, et il devient de plus en plus difficile de tester le bon composant dans le bon style - il y a trop de cordes à tirer.
Quelqu'un crache et entre dans le monde peu profond. Quelqu'un a agité la main lors des tests unitaires et a tout transféré à Cypress (marche comme marche!).


Quelqu'un d'autre pointe du doigt la réaction, dit que ce sont des effets algébriques et que vous pouvez faire ce que vous voulez. Tous les exemples ci-dessus sont essentiellement l'utilisation de ces effets et simulations algébriques . Pour moi et DI, ce sont des moki.


PS: Ce message a été écrit en réponse aux commentaires dans React / RFC sur le fait que l'équipe React a tout cassé, et tous les polymères là-bas aussi
PPS: Ce message est en fait une traduction très gratuite d'un autre
PPPS: en général, pour un véritable isolement, regardez rewiremock

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


All Articles