Fermer

avril 29, 2018

5 Réagir sur les meilleures pratiques d'architecture –


Il ne fait aucun doute que React a révolutionné la façon dont nous construisons les interfaces utilisateur. Il est facile à apprendre et facilite grandement la création de composants réutilisables qui offrent à votre site un aspect cohérent.

Cependant, comme React ne prend en charge que la couche d'affichage d'une application, elle n'applique aucune architecture spécifique (telle que MVC ou MVVM). Cela peut rendre difficile de garder votre base de code organisée comme votre projet React se développe.

Ici à 9éléments (où je suis PDG), un de nos produits phares est PhotoEditorSDK – un éditeur de photos entièrement personnalisable qui s'intègre facilement dans votre application HTML5, iOS ou Android. PhotoEditorSDK est une application React à grande échelle, destinée aux développeurs. Il nécessite de hautes performances, de petites constructions, et doit être très flexible en termes de style et surtout de thématique.

Tout au long des nombreuses itérations de PhotoEditorSDK, mon équipe et moi avons sélectionné un certain nombre de bonnes pratiques pour organiser une grande application React , dont nous aimerions partager avec vous dans cet article.

1. Disposition du répertoire

À l'origine, le style et le code de nos composants étaient séparés. Tous les styles ont vécu dans un fichier CSS partagé (nous utilisons SCSS pour le pré-traitement). Le composant actuel (dans ce cas FilterSlider ), a été découplé des styles:

 ├── composants
Filter └── FiltreSlider
│ ├── __tests__
│ │ └── FilterSlider-test.js
│ └── FilterSlider.jsx
└── styles
    └── photo-editor-sdk.scss

Au cours de multiples refactorings, nous avons expérimenté que cette approche ne s'est pas très bien adaptée. À l'avenir, nos composants doivent être partagés entre plusieurs projets internes, comme le SDK et un outil de texte expérimental que nous sommes en train de développer. Nous sommes donc passés à une disposition de fichier centrée sur les composants:

 composants
    └── FilterSlider
        ├── __tests__
        │ └── FilterSlider-test.js
        ├── FilterSlider.jsx
        └── FilterSlider.scss

L'idée était que tout le code qui appartient à un composant (par exemple JavaScript, CSS, assets, tests) se trouve dans un seul dossier. Cela rend très facile l'extraction du code dans un module npm ou, dans le cas où vous êtes pressé, simplement partager le dossier avec un autre projet.

Importation de composants

Un des inconvénients de cette structure de répertoire est Pour importer des composants, vous devez importer le chemin qualifié complet, comme suit:

 import FilterSlider from 'components / FilterSlider / FilterSlider'

Mais ce que nous aimerions vraiment écrire c'est:

 importer FilterSlider depuis 'components / FilterSlider'

L'approche naïve pour résoudre ce problème consiste à changer le fichier du composant en index.js :

 composants
    └── FilterSlider
        ├── __tests__
        │ └── FilterSlider-test.js
        ├── FilterSlider.scss
        Index── index.jsx

Malheureusement, lors du débogage des composants React dans Chrome et une erreur se produit, le débogueur affiche beaucoup de fichiers index.js ce qui rend cette option impossible.

Une autre approche nous avons essayé le répertoire-webpack-plugin . Ce plugin crée une petite règle dans le résolveur webpack pour rechercher un fichier JavaScript ou JSX avec le même nom que le répertoire à partir duquel il est importé. L'inconvénient de cette approche est le verrouillage du fournisseur à webpack. C'est sérieux, parce que Rollup est un peu meilleur pour regrouper les bibliothèques. En outre, la mise à jour vers les versions récentes de webpack était toujours un problème.

La solution que nous avons trouvée est un peu plus étendue, mais elle utilise un mécanisme de résolution standard Node.js, le rendant solide et pérenne. Tout ce que nous faisons est d'ajouter un fichier package.json à la structure du fichier:

 composants
    └── FilterSlider
        ├── __tests__
        │ └── FilterSlider-test.js
        ├── FilterSlider.jsx
        ├── FilterSlider.scss
        Package── package.json

Et dans package.json nous utilisons la propriété principale pour définir notre point d'entrée sur le composant, comme ceci:

 {
  "main": "FilterSlider.jsx"
}

Avec cet ajout, nous pouvons importer un composant comme ceci:

 import FilterSlider from 'components / FilterSlider'

2. CSS en JavaScript

Le style, et en particulier les thèmes, a toujours été un peu un problème. Comme mentionné ci-dessus, dans notre première itération de l'application, nous avions un gros fichier CSS (SCSS), dans lequel toutes nos classes vivaient. Pour éviter les collisions de noms, nous avons utilisé un préfixe global et suivi les conventions BEM pour créer des noms de règles CSS. Lorsque notre application s'est développée, cette approche n'a pas très bien évolué, nous avons donc cherché un remplacement. Nous avons d'abord évalué modules CSS mais à ce moment-là, ils avaient des problèmes de performance. De plus, l'extraction du CSS via le plugin Extract Text de webpack ne fonctionnait pas très bien (ça devrait être OK au moment de l'écriture). De plus, cette approche a créé une forte dépendance au webpack et rendu les tests assez difficiles.

Nous avons ensuite évalué certaines des autres solutions CSS-in-JS récemment arrivées sur la scène:

Le choix d'une de ces bibliothèques dépend fortement sur votre cas d'utilisation:

  1. Avez-vous besoin de la bibliothèque pour cracher un fichier CSS compilé pour la production? EmotionJS peut le faire!
  2. Avez-vous des problèmes de thème compliqués? Styled Components et Glamorous sont vos amis!
  3. A-t-il besoin de fonctionner sur le serveur? Ce n'est pas un problème pour les versions récentes de toutes les bibliothèques!

Pour le PhotoEditorSDK, nous avons fini par créer Adonis – notre propre solution CSS-in-JS. Sentez-vous libre de vérifier le projet et d'évaluer s'il répond à vos besoins, mais pour être honnête, nous ne pouvons pas recommander cette approche à tout le monde. Pour la plupart des autres projets, nous utilisons normalement Styled Components car il est vraiment puissant et a une forte communauté derrière lui. Ceci est vraiment utile, surtout lorsque vous avez des problèmes de thème très complexes. Les plugins de communauté comme sur le thème du style sont une ressource inestimable.

Protip : Lorsque vous stylisez beaucoup de balises HTML comme ceci:

 const Wrapper = styled.section`
  rembourrage: 4em;
  arrière-plan: papayawhip;
`;

nous créons un fichier Atoms.jsx où nous mettons tous les composants de style:

 composants
    └── FilterSlider
        ├── __tests__
        │ └── FilterSlider-test.js
        At── Atoms.jsx
        ├── FilterSlider.jsx
        ├── FilterSlider.scss
        Package── package.json

C'est une bonne pratique de désencombrer votre fichier de composant principal

Viser la responsabilité unique des composants de réaction

Lorsque vous développez des composants d'interface utilisateur très abstraits, il est parfois difficile de séparer les problèmes. À certains moments, votre composant aura besoin d'une certaine logique de domaine de votre modèle, puis les choses deviendront désordonnées. Dans les sections suivantes, nous aimerions vous montrer certaines méthodes de SÉCHAGE de vos composants. Les techniques suivantes se chevauchent dans la fonctionnalité, et choisir le bon pour votre architecture est plus une préférence dans le style plutôt que basé sur des faits concrets. Mais permettez-moi d'abord d'introduire les cas d'utilisation:

  • Nous avons dû introduire un mécanisme pour traiter les composants qui sont sensibles au contexte de l'utilisateur connecté.
  • Nous devions rendre une table avec plusieurs éléments pliables
  • Nous avons dû afficher différents composants en fonction de différents états
  • Dans la section suivante, je vais montrer différentes solutions pour les problèmes décrits ci-dessus.

    3. Composants d'ordre supérieur (HOC)

    Parfois, vous devez vous assurer qu'un composant React n'est affiché que lorsqu'un utilisateur s'est connecté à votre application. Au départ, vous ferez des vérifications de santé mentale dans votre méthode de rendu jusqu'à ce que vous découvriez que vous vous répétez beaucoup. Sur votre mission pour sécher ce code, vous trouverez tôt ou tard des composants d'ordre supérieur qui vous aideront à extraire et à faire abstraction de certaines préoccupations d'un composant. En termes de développement de logiciel, les composants d'ordre supérieur sont une forme du modèle décorateur. Un composant d'ordre supérieur (HOC) est essentiellement une fonction qui reçoit un composant React en tant que paramètre et renvoie un autre composant React. Jetez un oeil à l'exemple suivant:

     import React, {Component} from 'react';
    importer PropTypes à partir de 'prop-types';
    import {connect} à partir de 'react-redux';
    import {push} de 'react-router-redux';
    
    exporter la fonction par défaut requiresAuth (WrappedComponent) {
      class AuthenticatedComponent extends Component {
        propTypes statiques = {
          utilisateur: PropTypes.object,
          envoi: PropTypes.func.isRequired
        }
    
        componentDidMount () {
          this._checkAndRedirect ();
        }
    
        componentDidUpdate () {
          this._checkAndRedirect ();
        }
    
        _checkAndRedirect () {
          const {dispatch, utilisateur} = this.props;
    
          if (! user) {
            envoi (push ('/ signin'));
          }
        }
    
        render () {
          revenir (
            
              {this.props.user? : null}         
          )     }   }   const wrappedComponentName = WrappedComponent.displayName || WrappedComponent.name || 'Composant';   AuthenticatedComponent.displayName = `Authentifié ($ {wrappedComponentName})`;   const mapStateToProps = (état) => {     revenir {       utilisateur: state.account.user     }   }   return connect (mapStateToProps) (Composant authentifié); }

    La fonction requiresAuth reçoit un composant ( WrappedComponent ) en tant que paramètre, qui sera décoré avec la fonctionnalité souhaitée. À l'intérieur de cette fonction, la classe AuthenticatedComponent restitue ce composant et ajoute la fonctionnalité pour vérifier si un utilisateur est présent, sinon il redirige vers la page de connexion. Enfin, ce composant est connecté à un magasin Redux et renvoyé. Redux est utile, dans cet exemple, mais pas absolument nécessaire.

    ProTip : C'est sympa de mettre le displayName du composant à quelque chose comme fonctionnalité (originalcomponentName) de sorte que, lorsque vous avez beaucoup de composants décorés, vous pouvez facilement les distinguer dans le débogueur.

    4. Fonction comme enfants

    Créer une ligne de table pliable n'est pas une tâche très simple. Comment restituez-vous le bouton d'effondrement? Comment allons-nous afficher les enfants lorsque la table n'est pas effondrée? Je sais qu'avec JSX 2.0 les choses sont devenues beaucoup plus faciles, car vous pouvez retourner un tableau au lieu d'un seul tag, mais je vais développer cet exemple, car il illustre un bon cas d'utilisation pour la fonction en tant qu'enfants modèle. Imaginez le tableau suivant:

     import React, {Component} from "react";
    
    exporter la classe par défaut Table extends Component {
      render () {
        revenir (
          
             {this.props.children}
          
    Juste une table
        )   } }

    Et un corps de table pliable:

     import React, {Component} de "react";
    
    exporter la classe par défaut CollapsibleTableBody extends Component {
      constructeur (accessoires) {
        super (les accessoires);
        this.state = {replié: faux};
      }
    
      toggleCollapse = () => {
        this.setState ({replié:! this.state.collapsed});
      }
    
      render () {
        revenir (
          
            {this.props.children (this.state.collapsed, this.toggleCollapse)}
          
        )
      }
    }
    

    Vous utiliseriez ce composant de la manière suivante:

     
      
        {(replapsed, toggleCollapse) => {
          if (réduit) {
            revenir (
              
            );
          } autre {
            revenir (
              
            );
          }
        }}
      
    CollapsedContent

    Vous passez simplement une fonction en tant qu'enfant, qui est appelée dans la fonction de rendu du composant . Vous avez peut-être également vu cette technique appelée «callback de rendu» ou, dans des cas spéciaux, comme «render prop».

    5. Render Props

    Le terme "render prop" a été inventé par Michael Jackson, qui a suggéré que le modèle de composant d'ordre supérieur pourrait être remplacé 100% du temps avec un composant régulier avec un "render prop" . L'idée de base ici est de passer un composant React dans une fonction appelable comme une propriété et d'appeler cette fonction dans la fonction de rendu.

    Regardez ce code, qui tente de généraliser comment extraire des données d'une API:

     import React, {Component} de 'react';
    importer PropTypes à partir de 'prop-types';
    
    exporter la classe par défaut Fetch extends Component {
      propTypes statiques = {
        rendu: PropTypes.func.isRequired,
        url: PropTypes.string.isRequired,
      }
    
      état = {
        Les données: {},
        isLoading: faux,
      }
    
      _fetch = async () => {
        const res = wait fetch (this.props.url);
        const json = attend res.json ();
    
        this.setState ({
          données: json,
          isLoading: faux,
        });
      }
    
      componentDidMount () {
        this.setState ({isLoading: true}, this._fetch);
      }
    
      render () {
        retourne this.props.render (this.state);
      }
    }
    

    Comme vous pouvez le voir, il existe une propriété appelée render qui est une fonction appelée pendant le processus de rendu. La fonction appelée à l'intérieur obtient l'état complet comme paramètre et renvoie JSX. Maintenant, regardez l'utilisation suivante:

      (
        

    img.ly repos

          {isLoading &&

    Chargement en cours ...

    }       
              {data.length> 0 && data.map (repo => (           
    •             {repo.full_name}           
    •         ))}       
    )} />

    Comme vous pouvez le constater, les paramètres data et isLoading sont déstructurés de l'objet state et peuvent être utilisés pour piloter la réponse du JSX. Dans ce cas, tant que la promesse n'a pas été remplie, un titre "Chargement" est affiché. C'est à vous de décider quelles parties de l'état vous passez au prop de rendu et comment vous les utilisez dans votre interface utilisateur. Dans l'ensemble, c'est un mécanisme très puissant pour extraire un comportement commun. Le motif Fonction comme enfants décrit ci-dessus est fondamentalement le même modèle où la propriété est enfants

    Protip : Depuis le rendu prop ] motif est une généralisation du motif Fonction enfants rien ne vous empêche d'avoir plusieurs accessoires de rendu sur un composant. Par exemple, un composant Table pourrait avoir un accessoire de rendu pour l'en-tête, puis un autre pour le corps.

    Continuons la discussion

    J'espère que vous avez apprécié ce post sur les motifs architecturaux. S'il vous manque quelque chose dans cet article (il y a certainement plus de bonnes pratiques), ou si vous voulez juste entrer en contact, s'il vous plaît ping moi sur Twitter .

    Et si jamais vous considérez mise en œuvre de retouche photo dans votre application web, s'il vous plaît consulter notre produit. Le PhotoEditorSDK est un outil puissant et multi-facettes qui vous permet de fournir à vos utilisateurs des capacités de retouche photo hautes performances. Nous avons consacré des années de développement à la création de la meilleure solution et nous sommes très fiers de ce que nous avons accompli.






    Source link