Composition des composants dans React JS

Après 2 ans de travail avec React, j'ai une expérience que j'aimerais partager. Si vous venez de commencer à maîtriser React, j'espère que cet article vous aidera à choisir la bonne voie pour développer un projet de 1 à 5 formulaires à un vaste ensemble de composants et en même temps à ne pas vous perdre.



Si vous êtes déjà un pro, souvenez-vous peut-être de vos béquilles. Ou peut-être offrir de meilleures solutions aux problèmes décrits.



Cet article se concentrera sur mon opinion personnelle sur la façon d'organiser la composition des composants.

Commençons petit



Considérons une forme abstraite. Nous voudrons dire qu'il y a beaucoup de champs dans le formulaire (environ 10-15 pièces), mais pour que les yeux ne se lèvent pas, nous prendrons un formulaire avec 4 champs à titre d'exemple.



Un objet multiniveau du type suivant arrive à l'entrée du composant:



const unit = {
  name: 'unit1',
  color: 'red',
  size: {
    width: 2,
    height: 4,
  },
}


Un développeur inexpérimenté (comme moi au cours du premier mois de travail avec react) fera tout cela dans un seul composant où les valeurs d'entrée seront stockées dans l'état:



const Component = ({ values, onSave, onCancel }) => {
  const [ state, setState ] = useState({});

  useEffect(() => {
    setState(values);
  }, [ values, setState ]);

  return <div className="form-layout">
    <div className="form-field">
      <Input onChange={({ target: { value } }) =>
        setState((state) => ({...state, name: value }))
      }/>
    </div>
    <div className="form-field">
      <Input onChange={({ target: { value } }) =>
        setState((state) => ({...state, color: value }))
      }/>
    </div>
    <div className="size">
      <div className="form-field">
        <Input onChange={({ target: { value } }) =>
          setState((state) => ({...state, size: { width: value } }))
        }/>
      </div>
      <div className="form-field">
        <Input onChange={({ target: { value } }) =>
          setState((state) => ({...state, size: { height: value } }))
        }/>
      </div>
    </div>
    <div className="buttons">
      <Button onClick={() => onSave(state)}>Save</Button>
      <Button onClick={() => onCancel()}>Cancel</Button>
    </div>
  </div>
}


Voyant à quelle vitesse le développeur y a fait face, le client proposera d'en faire un autre basé sur ce formulaire, mais sans le bloc «taille».



Il existe 2 options (et les deux ne sont pas correctes):



  1. Vous pouvez copier le premier composant et ajouter ce qui manque ou supprimer l'excédent. Cela se fait généralement lorsqu'ils ne prennent pas leur propre composant comme base et qu'ils ont peur d'y casser quelque chose.
  2. Ajoutez des paramètres de composant supplémentaires aux paramètres.


Si, après la mise en œuvre de 3 à 5 formulaires, le projet est terminé, le développeur a de la chance.



Mais généralement, ce n'est que le début et le nombre de moisissures différentes ne fait qu'augmenter.



Ensuite, un similaire est requis, mais sans le bloc «couleur».

Puis un similaire, mais avec un nouveau bloc "description".

Ensuite, créez des blocs pour la lecture uniquement.

Ensuite, une forme similaire doit être insérée dans une autre forme - en général, la tristesse, qui en sort parfois.



Nouveaux formulaires en copiant



Un développeur qui adopte une approche de copie sera bien sûr rapide à mettre en œuvre de nouveaux formulaires. Certes, il y en aura moins de 10. Mais alors l'ambiance diminuera progressivement.



Surtout quand une refonte se produit. Ajustez l'indentation entre les blocs de la forme "un peu", changez le composant de sélection de couleur. Après tout, tout ne peut pas être prévu à la fois et de nombreuses décisions de conception devront être révisées après leur mise en œuvre.



Il est important de prêter attention à la mention fréquente de «forme similaire». Après tout, le produit est un et tous les moules doivent être similaires. En conséquence, vous devez faire face à un travail de routine très inintéressant pour retravailler le même dans chaque formulaire, et en passant, les testeurs devront également vérifier chaque formulaire.

En général, vous voyez l'idée. Ne copiez pas des composants similaires.


Nouvelles formes par généralisation



Si le développeur a choisi la deuxième voie, alors bien sûr, il est à cheval - vous pourriez penser. Il ne comporte que quelques composants avec lesquels vous pouvez dessiner des dizaines de formes. Corrigez l'indentation tout au long du projet ou changez le composant «couleur» - il s'agit de corriger deux lignes dans le code et le testeur devra revérifier à quelques endroits seulement.



Mais en fait, cette voie a donné lieu à une composante très difficile à maintenir.



Il est difficile de l'utiliser, tk. de nombreux paramètres, dont certains sont appelés à peu près les mêmes, pour comprendre de quoi chaque paramètre est responsable, vous devez aller dans les entrailles.



<Component
  isNameVisible={true}
  isNameDisabled={true}
  nameLabel="Model"
  nameType="input"
  isColorVisible={true}
  isColorDisabled={false}
  colorType={'dropdown'}
  isSizeVisible={true}
  isHeightVisible={true}
  isWidthDisabled={false}
/>


C'est également difficile à maintenir. En règle générale, il y a des conditions complexes d'entrelacement à l'intérieur et l'ajout d'une nouvelle condition peut tout casser. Ajuster le composant pour produire un formulaire peut casser le reste.

En général, vous voyez l'idée. Ne faites pas de nombreuses propriétés au composant.
Pour résoudre les problèmes de la deuxième option, les développeurs commencent quoi? Droite. En tant que vrais développeurs, ils commencent à développer quelque chose qui facilite la mise en place d'un composant complexe.

Par exemple, ils créent le paramètre fields (enfin, comme les colonnes de react-table). Et les paramètres des champs y sont passés: quel champ est visible, qui n'est pas modifiable, le nom du champ.



L'appel de composant se transforme en ceci:



const FIELDS = {
    name: { visible: true, disabled: true, label: 'Model', type: 'input' },
    color: { visible: true, disabled: false, type: 'dropdown' },
    size: { visible: true },
    height: { visible: true },
    width: { disabled: false },
}
<Component
  values={values}
  fields={FIELDS}
/>


En conséquence, le développeur est fier de lui. Il a généralisé les paramètres pour tous les champs et optimisé le code interne du composant: maintenant pour chaque champ une fonction est appelée, ce qui convertit la configuration en accessoires du composant correspondant. Même par nom de type, un composant différent est rendu. Un peu plus et vous obtiendrez votre propre cadre.



Cool? Trop.



Espérons que cela ne se transforme pas en ceci:



const FIELDS = {
    name: getInputConfig({ visible: true, disabled: true, label: 'Model'}),
    color: getDropDownConfig({ visible: true, disabled: false}),
    size: getBlockConfig({ visible: true }),
    height: getInputNumberConfig({ visible: true }),
    width: getInputNumberConfig({ disabled: false }),
}
<Component
  values={values}
  fields={FIELDS}
/>


En général, vous voyez l'idée. Ne réinventez pas la roue.


Nouvelles formes en composant des composants et des formes imbriquées



Souvenons-nous de ce sur quoi nous écrivons encore. Nous avons déjà une bibliothèque de réaction. Il n'est pas nécessaire d'inventer de nouveaux modèles. La configuration des composants dans react est décrite à l'aide du balisage JSX




const Form1 = ({ values }) => {
  return <FormPanel>
    <FormField disabled label=”Model”>
      <Input name="name" />
    </FormField>
    <FormField disabled label=”Color”>
      <DropDown name="color" />
    </FormField>
    <FormPanel>
      <FormField disabled label="Height">
        <Input name="height" />
      </FormField>
      <FormField disabled label="Width">
        <Input name="width" />
     </From Field>
    </FormPanelt>
  </FormPanel>
}


Il semble que nous soyons revenus à la première option de copie. Mais en fait non. Il s'agit d'une composition qui élimine les problèmes des deux premières approches.



Il y a un ensemble de briques à partir desquelles la forme est assemblée. Chaque brique est responsable de quelque chose de différent. Certains pour la mise en page et l'apparence, certains pour la saisie de données.



Si vous devez modifier les retraits dans l'ensemble du projet, il suffit de le faire dans le composant FormField. Si vous devez modifier le fonctionnement de la liste déroulante, cela se fait en un seul endroit dans le composant DropDown.



Si vous avez besoin d'une forme similaire, mais par exemple pour qu'il n'y ait pas de champ «couleur», nous plaçons les blocs communs dans des briques séparées et assemblons une autre forme.



Nous déplaçons le bloc Taille dans un composant séparé:



const Size = () =>  <FormPanel>
    <FormField disabled label="Height">
      <Input name="height" />
    </FormField>
    <FormField disabled label=”Width”>
      <Input name="width" />
   </From Field>
  </FormPanel>


Faire une forme avec un choix de couleurs:



const Form1 = () => <FormPanel>
    <FormField disabled label="Color">
      <DropDown name="color" />
   </FormField>
    <FormField disabled label="Model">
       <Input name="name" />
    </FormField>
    <Size name="size" />
</FormPanel>


Nous fabriquons une forme similaire, mais sans choisir de couleur:



const Form2 = () => <FormPanel>
    <FormField disabled label="Model">
       <Input name="name" />
    </FormField>
    <Size name="size" />
</FormPanel>


Plus important encore, la personne qui obtient un tel code n'a pas besoin de gérer les configurations fictives du prédécesseur. Tout est écrit en JSX, familier à tout développeur de réaction, avec des indications de paramètres pour chaque composant.

En général, vous voyez l'idée. Utilisez JSX et la composition des composants.


Quelques mots sur l'état



Faisons maintenant attention à l'État. Plus précisément, son absence. Dès que l'on ajoute un état, on ferme le flux de données et il devient plus difficile de réutiliser le composant. Toutes les briques doivent être apatrides (c'est-à-dire apatrides). Et seulement au niveau le plus élevé, la forme assemblée à partir de briques peut être connectée à l'état. Si le formulaire est complexe, il est logique de le diviser en plusieurs conteneurs et de connecter chaque partie à redux.



Ne soyez pas paresseux pour créer un composant de formulaire sans état distinct. Ensuite, vous aurez la possibilité de l'utiliser dans le cadre d'un autre formulaire, ou de créer un formulaire avec état basé sur celui-ci, ou un conteneur pour vous connecter à redux.



Bien sûr, dans les briques, il peut y avoir des états pour stocker un état interne non lié au flux de données général. Par exemple, dans l'état interne DropDown (liste déroulante), il est pratique de stocker l'attribut, qu'il soit développé ou non.

En général, vous voyez l'idée. Séparez les composants en Stateless et Statefull.


Total



Sans surprise, je rencontre périodiquement toutes les erreurs décrites dans l'article et les problèmes qui en découlent. J'espère que vous ne les répéterez pas et que la maintenance de votre code deviendra beaucoup plus facile.



Je vais répéter les principaux points:



  1. Ne copiez pas des composants similaires. Utilisez le principe DRY.
  2. Ne créez pas de composants avec beaucoup de propriétés et de fonctionnalités. Chaque composant doit être responsable de quelque chose de différent (responsabilité unique de SOLID)
  3. Séparez les composants en Stateless et Statefull.
  4. Ne réinventez pas vos créations. Utilisez JSX et la composition de vos composants.



All Articles