Meilleures pratiques React





DĂ©veloppez-vous avec React ou ĂȘtes-vous simplement intĂ©ressĂ© par cette technologie? Alors bienvenue dans mon nouveau projet - Total React .



introduction



Je travaille avec React depuis 5 ans, cependant, quand il s'agit de la structure de l'application ou de son apparence (design), il est difficile de nommer des approches universelles.



Dans le mĂȘme temps, il existe certaines techniques de codage qui vous permettent d'assurer un support et une Ă©volutivitĂ© Ă  long terme de vos projets.



Cet article est en quelque sorte un ensemble de rÚgles de développement d'applications React qui se sont avérées efficaces pour moi et les équipes avec lesquelles j'ai travaillé.



Ces rĂšgles couvrent les composants, la structure de l'application, les tests, le style, la gestion de l'Ă©tat et la rĂ©cupĂ©ration des donnĂ©es. Les exemples fournis sont volontairement simplifiĂ©s afin de se concentrer sur des principes gĂ©nĂ©raux plutĂŽt que sur une mise en Ɠuvre spĂ©cifique.



Les approches proposĂ©es ne sont pas la vĂ©ritĂ© ultime. C'est juste mon opinion. Il existe de nombreuses façons diffĂ©rentes d'accomplir la mĂȘme tĂąche.



Composants



Composants fonctionnels


Donnez la prĂ©fĂ©rence aux composants fonctionnels - ils ont une syntaxe plus simple. Ils manquent de mĂ©thodes de cycle de vie, de constructeurs et de code standard. Ils vous permettent d'implĂ©menter la mĂȘme logique que les composants de classe, mais avec moins d'effort et de maniĂšre plus descriptive (le code du composant est plus facile Ă  lire).



Utilisez des composants fonctionnels jusqu'Ă  ce que vous ayez besoin de fusibles. Le modĂšle mental Ă  garder Ă  l'esprit sera beaucoup plus simple.



//     ""
class Counter extends React.Component {
  state = {
    counter: 0,
  }

  constructor(props) {
    super(props)
    this.handleClick = this.handleClick.bind(this)
  }

  handleClick() {
    this.setState({ counter: this.state.counter + 1 })
  }

  render() {
    return (
      <div>
        <p> : {this.state.counter}</p>
        <button onClick={this.handleClick}></button>
      </div>
    )
  }
}

//       
function Counter() {
  const [counter, setCounter] = useState(0)

  handleClick = () => setCounter(counter + 1)

  return (
    <div>
      <p> : {counter}</p>
      <button onClick={handleClick}></button>
    </div>
  )
}

      
      





Composants cohérents (séquentiels)


Respectez le mĂȘme style lors de la crĂ©ation de composants. Placez les fonctions d'assistance au mĂȘme endroit, utilisez la mĂȘme exportation (par dĂ©faut ou par nom) et utilisez la mĂȘme convention de dĂ©nomination pour les composants.



Chaque approche a ses propres avantages et inconvénients.



Peu importe la façon dont vous exportez les composants, tout en bas ou dans la définition, respectez simplement une rÚgle.



Noms des composants


Nommez toujours les composants. Cela aide à analyser la trace de la pile d'erreurs lors de l'utilisation des outils de développement React.



Il vous aide également à déterminer le composant que vous développez actuellement.



//    
export default () => <form>...</form>

//    
export default function Form() {
  return <form>...</form>
}

      
      





Fonctions secondaires


Les fonctions qui ne nĂ©cessitent pas les donnĂ©es stockĂ©es dans le composant doivent ĂȘtre Ă  l'extĂ©rieur (Ă  l'extĂ©rieur) du composant. L'endroit idĂ©al pour cela est avant la dĂ©finition du composant, afin que le code puisse ĂȘtre examinĂ© de haut en bas.



Cela réduit le «bruit» du composant - il ne reste que l'essentiel.



//    
function Component({ date }) {
  function parseDate(rawDate) {
    ...
  }

  return <div> {parseDate(date)}</div>
}

//      
function parseDate(date) {
  ...
}

function Component({ date }) {
  return <div> {parseDate(date)}</div>
}

      
      





Il doit y avoir un nombre minimum de fonctions auxiliaires à l'intérieur du composant. Placez-les à l'extérieur, en passant les valeurs de l'état comme arguments.



En suivant les rÚgles de création de fonctions «propres», il est plus facile de suivre les erreurs et d'étendre le composant.



//      ""    
export default function Component() {
  const [value, setValue] = useState('')

  function isValid() {
    // ...
  }

  return (
    <>
      <input
        value={value}
        onChange={e => setValue(e.target.value)}
        onBlur={validateInput}
      />
      <button
        onClick={() => {
          if (isValid) {
            // ...
          }
        }}
      >
        
      </button>
    </>
  )
}

//          
function isValid(value) {
  // ...
}

export default function Component() {
  const [value, setValue] = useState('')

  return (
    <>
      <input
        value={value}
        onChange={e => setValue(e.target.value)}
        onBlur={validateInput}
      />
      <button
        onClick={() => {
          if (isValid(value)) {
            // ...
          }
        }}
      >
        
      </button>
    </>
  )
}

      
      





Balisage statique (dur)


Ne créez pas de balisage statique pour la navigation, les filtres ou les listes. Au lieu de cela, créez un objet avec des paramÚtres et faites une boucle dessus.



Cela signifie que vous ne devez modifier le balisage et les éléments qu'à un seul endroit, si nécessaire.



//     
function Filters({ onFilterClick }) {
  return (
    <>
      <p> </p>
      <ul>
        <li>
          <div onClick={() => onFilterClick('fiction')}> </div>
        </li>
        <li>
          <div onClick={() => onFilterClick('classics')}>
            
          </div>
        </li>
        <li>
          <div onClick={() => onFilterClick('fantasy')}></div>
        </li>
        <li>
          <div onClick={() => onFilterClick('romance')}></div>
        </li>
      </ul>
    </>
  )
}

//       
const GENRES = [
  {
    identifier: 'fiction',
    name: ' ',
  },
  {
    identifier: 'classics',
    name: '',
  },
  {
    identifier: 'fantasy',
    name: '',
  },
  {
    identifier: 'romance',
    name: '',
  },
]

function Filters({ onFilterClick }) {
  return (
    <>
      <p> </p>
      <ul>
        {GENRES.map(genre => (
          <li>
            <div onClick={() => onFilterClick(genre.identifier)}>
              {genre.name}
            </div>
          </li>
        ))}
      </ul>
    </>
  )
}

      
      





Dimensions des composants


Un composant est juste une fonction qui prend des accessoires et renvoie un balisage. Ils suivent les mĂȘmes principes de conception.



Si une fonction effectue trop de tĂąches, dĂ©placez une partie de la logique vers une autre fonction. Il en va de mĂȘme pour les composants: si un composant contient des fonctionnalitĂ©s trop complexes, divisez-le en plusieurs composants.



Si une partie du balisage est complexe, comprend des boucles ou des conditions, extrayez-la dans un composant séparé.



Fiez-vous aux accessoires et aux rappels pour l'interaction et la rĂ©cupĂ©ration de donnĂ©es. Le nombre de lignes de code n'est pas toujours un critĂšre objectif de sa qualitĂ©. N'oubliez pas d'ĂȘtre rĂ©actif et abstrait.



Commentaires dans JSX


Si vous avez besoin d'une explication sur ce qui se passe, créez un bloc de commentaires et ajoutez-y les informations nécessaires. Le balisage fait partie de la logique, donc si vous sentez que vous avez besoin de commenter une piÚce, faites-le.



function Component(props) {
  return (
    <>
      {/*    ,       */}
      {user.subscribed ? null : <SubscriptionPlans />}
    </>
  )
}

      
      





Disjoncteurs


Une erreur dans le composant ne doit pas casser l'interface utilisateur. Il existe de rares cas oĂč nous voulons qu'une erreur critique entraĂźne un blocage ou une redirection de l'application. Dans la plupart des cas, il suffit de supprimer un certain Ă©lĂ©ment de l'Ă©cran.



Dans une fonction demandant des données, nous pouvons avoir n'importe quel nombre de blocs try / catch. Utilisez des fusibles non seulement au niveau supérieur de votre application, mais enveloppez chaque composant qui pourrait potentiellement lever une exception pour éviter une cascade d'erreurs.



function Component() {
  return (
    <Layout>
      <ErrorBoundary>
        <CardWidget />
      </ErrorBoundary>

      <ErrorBoundary>
        <FiltersWidget />
      </ErrorBoundary>

      <div>
        <ErrorBoundary>
          <ProductList />
        </ErrorBoundary>
      </div>
    </Layout>
  )
}

      
      





Accessoires de destruction


La plupart des composants sont des fonctions qui acceptent des accessoires et renvoient des balises. Dans une fonction normale, nous utilisons des arguments qui lui sont directement passés, donc dans le cas des composants, il est logique de suivre une approche similaire. Il n'est pas nécessaire de répéter les «accessoires» partout.



La raison de ne pas utiliser la dĂ©structuration pourrait ĂȘtre la diffĂ©rence entre les Ă©tats externe et interne. Cependant, dans une fonction normale, il n'y a aucune diffĂ©rence entre les arguments et les variables. Vous n'avez pas besoin de compliquer les choses.



//    "props"   
function Input(props) {
  return <input value={props.value} onChange={props.onChange} />
}

//        
function Component({ value, onChange }) {
  const [state, setState] = useState('')

  return <div>...</div>
}

      
      





Nombre d'accessoires


La réponse à la question sur le nombre d'accessoires est trÚs subjective. Le nombre d'accessoires transmis à un composant est corrélé au nombre de variables utilisées par le composant. Plus le nombre d'accessoires est transmis au composant, plus sa responsabilité est élevée (c'est-à-dire le nombre de tùches résolues par le composant).



Un grand nombre d'accessoires peut indiquer que le composant en fait trop.



Si plus de 5 accessoires sont passĂ©s Ă  un composant, je pense Ă  la nĂ©cessitĂ© de le diviser. Dans certains cas, le composant a juste besoin de beaucoup de donnĂ©es. Par exemple, un champ de saisie de texte peut nĂ©cessiter de nombreux accessoires. D'un autre cĂŽtĂ©, c'est un signe certain qu'une partie de la logique doit ĂȘtre extraite dans un composant sĂ©parĂ©.



Attention: plus un composant reçoit d'accessoires, plus il est redessiné souvent.



Passer un objet au lieu de primitives


Une façon de réduire le nombre d'accessoires passés est de passer un objet au lieu de primitives. Au lieu, par exemple, de transmettre le nom de l'utilisateur, son adresse e-mail, etc. un par un, vous pouvez les regrouper. Cela facilitera également l'ajout de nouvelles données.



//      
<UserProfile
  bio={user.bio}
  name={user.name}
  email={user.email}
  subscription={user.subscription}
/>

//   ,  
<UserProfile user={user} />

      
      





Rendu conditionnel


Dans certains cas, l'utilisation de calculs courts (l'opérateur ET logique &&) pour le rendu conditionnel peut entraßner l'affichage de 0 dans l'interface utilisateur. Pour éviter cela, utilisez l'opérateur ternaire. Le seul inconvénient de cette approche est un peu plus de code.



L'opérateur && réduit la quantité de code, ce qui est excellent. Ternarnik est plus "verbeux", mais il fonctionne toujours correctement. De plus, ajouter une alternative au besoin prend moins de temps.



//     
function Component() {
  const count = 0

  return <div>{count && <h1>: {count}</h1>}</div>
}

//   ,   
function Component() {
  const count = 0

  return <div>{count ? <h1>: {count}</h1> : null}</div>
}

      
      





Opérateurs ternaires imbriqués


Les opérateurs ternaires deviennent difficiles à lire aprÚs le premier niveau d'imbrication. Bien que les ternaires économisent de l'espace, il est préférable d'exprimer leurs intentions de maniÚre explicite et évidente.



//     
isSubscribed ? (
  <ArticleRecommendations />
) : isRegistered ? (
  <SubscribeCallToAction />
) : (
  <RegisterCallToAction />
)

//      
function CallToActionWidget({ subscribed, registered }) {
  if (subscribed) {
    return <ArticleRecommendations />
  }

  if (registered) {
    return <SubscribeCallToAction />
  }

  return <RegisterCallToAction />
}

function Component() {
  return (
    <CallToActionWidget
      subscribed={subscribed}
      registered={registered}
    />
  )
}

      
      





Listes


Faire une boucle sur les éléments d'une liste est une tùche courante, généralement accomplie avec la méthode «map ()». Cependant, dans un composant qui contient beaucoup de balisage, l'indentation supplémentaire et la syntaxe "map ()" n'améliorent pas la lisibilité.



Si vous devez parcourir les Ă©lĂ©ments, extrayez-les dans un composant sĂ©parĂ©, mĂȘme si le balisage est petit. Le composant parent n'a pas besoin de dĂ©tails, il a seulement besoin de "savoir" que la liste est rendue Ă  un certain endroit.



L'itĂ©ration peut ĂȘtre laissĂ©e dans un composant dont le seul but est d'afficher la liste. Si le balisage de liste est complexe et long, il est prĂ©fĂ©rable de l'extraire dans un composant sĂ©parĂ©.



//       
function Component({ topic, page, articles, onNextPage }) {
  return (
    <div>
      <h1>{topic}</h1>
      {articles.map(article => (
        <div>
          <h3>{article.title}</h3>
          <p>{article.teaser}</p>
          <img src={article.image} />
        </div>
      ))}
      <div>    {page}</div>
      <button onClick={onNextPage}></button>
    </div>
  )
}

//      
function Component({ topic, page, articles, onNextPage }) {
  return (
    <div>
      <h1>{topic}</h1>
      <ArticlesList articles={articles} />
      <div>    {page}</div>
      <button onClick={onNextPage}></button>
    </div>
  )
}

      
      





Accessoires par défaut


Une façon de définir les accessoires par défaut consiste à ajouter un attribut "defaultProps" au composant. Cependant, avec cette approche, la fonction du composant et les valeurs de ses arguments seront à des endroits différents.



Par consĂ©quent, il est plus prĂ©fĂ©rable d'attribuer des valeurs "par dĂ©faut" lors de la dĂ©structuration des accessoires. Cela facilite la lecture du code de haut en bas et conserve les dĂ©finitions et les valeurs au mĂȘme endroit.



//          
function Component({ title, tags, subscribed }) {
  return <div>...</div>
}

Component.defaultProps = {
  title: '',
  tags: [],
  subscribed: false,
}

//      
function Component({ title = '', tags = [], subscribed = false }) {
  return <div>...</div>
}

      
      





Fonctions de rendu imbriquées


Si vous devez extraire la logique ou le balisage d'un composant, ne le placez pas dans une fonction du mĂȘme composant. Un composant est une fonction. Cela signifie que la partie extraite du code sera reprĂ©sentĂ©e comme une fonction imbriquĂ©e.



Cela signifie que la fonction imbriquée aura accÚs à l'état et aux données de la fonction externe. Cela rend le code moins lisible - que fait cette fonction (de quoi est-elle responsable)?



Déplacez la fonction imbriquée dans un composant séparé, donnez-lui un nom et utilisez des accessoires plutÎt que des fermetures.



//       
function Component() {
  function renderHeader() {
    return <header>...</header>
  }
  return <div>{renderHeader()}</div>
}

//      
import Header from '@modules/common/components/Header'

function Component() {
  return (
    <div>
      <Header />
    </div>
  )
}

      
      





Gestion d'état



BoĂźtes de vitesses


Parfois, nous avons besoin d'un moyen plus puissant pour définir et gérer l'état que "useState ()". Essayez d'utiliser "useReducer ()" avant d'utiliser des bibliothÚques tierces. C'est un excellent outil pour gérer des états complexes sans nécessiter de dépendances.



CombinĂ© avec context et TypeScript, useReducer () peut ĂȘtre trĂšs puissant. Malheureusement, il n'est pas utilisĂ© trĂšs souvent. Les gens prĂ©fĂšrent utiliser des bibliothĂšques spĂ©ciales.



Si vous avez besoin de plusieurs éléments d'état, déplacez-les vers le réducteur:



//       
const TYPES = {
  SMALL: 'small',
  MEDIUM: 'medium',
  LARGE: 'large'
}

function Component() {
  const [isOpen, setIsOpen] = useState(false)
  const [type, setType] = useState(TYPES.LARGE)
  const [phone, setPhone] = useState('')
  const [email, setEmail] = useState('')
  const [error, setError] = useSatte(null)

  return (
    // ...
  )
}

//      
const TYPES = {
  SMALL: 'small',
  MEDIUM: 'medium',
  LARGE: 'large'
}

const initialState = {
  isOpen: false,
  type: TYPES.LARGE,
  phone: '',
  email: '',
  error: null
}

const reducer = (state, action) => {
  switch (action.type) {
    ...
    default:
      return state
  }
}

function Component() {
  const [state, dispatch] = useReducer(reducer, initialState)

  return (
    // ...
  )
}

      
      





Crochets contre HOC et accessoires de rendu


Dans certains cas, nous devons "renforcer" un composant ou lui donner accÚs à des données externes. Il existe trois façons de le faire: les composants d'ordre supérieur (HOC), le rendu via des accessoires et des crochets.



Le moyen le plus efficace est d'utiliser des crochets. Ils sont entiĂšrement conformes Ă  la philosophie selon laquelle un composant est une fonction qui utilise d'autres fonctions. Les hooks vous permettent d'accĂ©der Ă  plusieurs sources contenant des fonctionnalitĂ©s externes sans risque de conflit entre ces sources. Le nombre de crochets n'a pas d'importance, nous savons toujours d'oĂč nous tirons la valeur.



Les HOC reçoivent des valeurs comme accessoires. Il n'est pas toujours Ă©vident d'oĂč proviennent les valeurs, du composant parent ou de son wrapper. De plus, l'enchaĂźnement de plusieurs accessoires est une source bien connue de bogues.



L'utilisation d'accessoires de rendu entraĂźne une imbrication profonde et une mauvaise lisibilitĂ©. Le fait de placer plusieurs composants avec des accessoires de rendu dans la mĂȘme arborescence aggrave encore la situation. De plus, ils n'utilisent que des valeurs dans le balisage, de sorte que la logique pour obtenir les valeurs doit ĂȘtre Ă©crite ici ou reçue de l'extĂ©rieur.



Dans le cas des hooks, nous travaillons avec des valeurs simples qui sont faciles à suivre et qui ne se mélangent pas avec JSX.



//    -
function Component() {
  return (
    <>
      <Header />
        <Form>
          {({ values, setValue }) => (
            <input
              value={values.name}
              onChange={e => setValue('name', e.target.value)}
            />
            <input
              value={values.password}
              onChange={e => setValue('password', e.target.value)}
            />
          )}
        </Form>
      <Footer />
    </>
  )
}

//   
function Component() {
  const [values, setValue] = useForm()

  return (
    <>
      <Header />
        <input
          value={values.name}
          onChange={e => setValue('name', e.target.value)}
        />
        <input
          value={values.password}
          onChange={e => setValue('password', e.target.value)}
        />
      <Footer />
    </>
  )
}

      
      





BibliothÚques pour obtenir des données


TrÚs souvent, les données de l'état "proviennent" de l'API. Nous devons les stocker en mémoire, les mettre à jour et les recevoir à plusieurs endroits.



Les bibliothĂšques modernes comme React Query fournissent une bonne quantitĂ© d'outils pour manipuler des donnĂ©es externes. Nous pouvons mettre en cache les donnĂ©es, les supprimer et en demander de nouvelles. Ces outils peuvent Ă©galement ĂȘtre utilisĂ©s pour envoyer des donnĂ©es, dĂ©clencher une mise Ă  jour d'une autre donnĂ©e, etc.



Travailler avec des données externes est encore plus facile si vous utilisez un client GraphQL comme Apollo . Il implémente le concept d'état client hors de la boßte.



BibliothÚques de gestion d'état


Dans la grande majorité des cas, nous n'avons besoin d'aucune bibliothÚque pour gérer l'état des applications. Ils ne sont nécessaires que dans les trÚs grandes applications avec un état trÚs complexe. Dans de telles situations, j'utilise l'une des deux solutions - Recoil ou Redux .



ModĂšles mentaux de composants



Conteneur et représentant


Habituellement, il est habituel de diviser les composants en deux groupes - représentants et conteneurs ou «intelligents» et «stupides».



L'essentiel est que certains composants ne contiennent ni Ă©tat ni fonctionnalitĂ©. Ils sont simplement appelĂ©s par le composant parent avec quelques accessoires. Le composant conteneur, Ă  son tour, contient une logique mĂ©tier, envoie des requĂȘtes pour recevoir des donnĂ©es et gĂšre l'Ă©tat.



Ce modÚle mental décrit en fait le modÚle de conception MVC pour les applications cÎté serveur. Elle y travaille trÚs bien.



Mais dans les applications clientes modernes, cette approche ne se justifie pas. Mettre toute la logique dans plusieurs composants conduit à des ballonnements excessifs. Cela conduit au fait qu'un composant résout trop de problÚmes. Le code d'un tel composant est difficile à maintenir. Au fur et à mesure que l'application se développe, maintenir le code dans un état approprié devient presque impossible.



Composants avec état et sans état


Divisez les composants en composants avec état et sans état. Le modÚle mental mentionné ci-dessus suggÚre qu'un petit nombre de composants doit piloter la logique de l'ensemble de l'application. Ce modÚle suppose la division de la logique en le nombre maximum possible de composants.



Les donnĂ©es doivent ĂȘtre aussi proches que possible du composant dans lequel elles sont utilisĂ©es. Lors de l'utilisation du client GrapQL, nous recevons des donnĂ©es dans un composant qui affiche ces donnĂ©es. MĂȘme si ce n'est pas un composant de premier niveau. Ne pensez pas aux conteneurs, pensez Ă  la responsabilitĂ© des composants. DĂ©terminer le composant le plus appropriĂ© pour contenir une partie de l'Ă©tat



Par exemple, un composant <Form /> doit contenir des données de formulaire. Le composant <Input /> doit recevoir des valeurs et appeler des rappels. Le composant <Button /> doit notifier au formulaire le souhait de l'utilisateur d'envoyer des données pour traitement, etc.



Qui est responsable de la validation du formulaire? Champ de saisie? Cela signifie que ce composant est responsable de la logique métier de l'application. Comment informera-t-il le formulaire d'une erreur? Comment l'état d'erreur sera-t-il mis à jour? Le formulaire "connaßtra-t-il" une telle mise à jour? En cas d'erreur, sera-t-il possible d'envoyer les données pour traitement?



Lorsque de telles questions se posent, il devient évident qu'il y a confusion des responsabilités. Dans ce cas, l '«entrée» est préférable de rester un composant sans état et de recevoir des messages d'erreur du formulaire.



Structure de l'application



Regroupement par route / module


Le regroupement par conteneurs et composants rend l'application difficile à maßtriser. Déterminer à quelle partie d'une application appartient un composant particulier suppose une familiarité "étroite" avec l'ensemble de la base de code.



Tous les composants ne sont pas identiques - certains sont utilisés dans le monde entier, d'autres sont conçus pour répondre à des besoins spécifiques. Cette structure convient aux petits projets. Cependant, pour des projets de taille moyenne à grande, une telle structure est inacceptable.



//        
├── containers
|   ├── Dashboard.jsx
|   ├── Details.jsx
├── components
|   ├── Table.jsx
|   ├── Form.jsx
|   ├── Button.jsx
|   ├── Input.jsx
|   ├── Sidebar.jsx
|   ├── ItemCard.jsx

//     /
├── modules
|   ├── common
|   |   ├── components
|   |   |   ├── Button.jsx
|   |   |   ├── Input.jsx
|   ├── dashboard
|   |   ├── components
|   |   |   ├── Table.jsx
|   |   |   ├── Sidebar.jsx
|   ├── details
|   |   ├── components
|   |   |   ├── Form.jsx
|   |   |   ├── ItemCard.jsx

      
      





DĂšs le dĂ©but, regroupez les composants par route / module. Cette structure permet un support et une expansion Ă  long terme. Cela empĂȘchera l'application de dĂ©passer son architecture. Si vous comptez sur "l'architecture conteneur-composant", cela se produira trĂšs rapidement.



L'architecture basée sur les modules est hautement évolutive. Vous ajoutez simplement de nouveaux modules sans augmenter la complexité du systÚme.



L '"architecture conteneur" n'est pas fausse, mais elle n'est pas trÚs générale (abstraite). Il ne dira à personne qui l'apprend autrement qu'il utilise React pour développer l'application.



Modules communs


Les composants tels que les boutons, les champs de saisie et les cartes sont omniprĂ©sents. MĂȘme si vous n'utilisez pas de framework basĂ© sur des composants, extrayez-les en composants partagĂ©s.



De cette façon, vous pouvez voir quels composants communs sont utilisĂ©s dans votre application, mĂȘme sans l'aide d'un Storybook . Cela Ă©vite la duplication de code. Vous ne voulez pas que chaque membre de votre Ă©quipe conçoive sa propre version du bouton, n'est-ce pas? Malheureusement, cela est souvent dĂ» Ă  une architecture d'application mĂ©diocre.



Chemins absolus


Les diffĂ©rentes parties de l'application doivent ĂȘtre changĂ©es aussi facilement que possible. Cela s'applique non seulement au code du composant, mais Ă©galement Ă  son emplacement. Les chemins absolus signifient que vous n'avez rien Ă  changer lorsque vous dĂ©placez le composant importĂ© vers un autre emplacement. Cela facilite Ă©galement la localisation du composant.



//     
import Input from '../../../modules/common/components/Input'

//      
import Input from '@modules/common/components/Input'

      
      





J'utilise le préfixe "@" comme indicateur du module interne, mais j'ai également vu des exemples d'utilisation du caractÚre "~".



Emballage de composants externes


Essayez de ne pas importer directement trop de composants tiers. En créant un adaptateur pour ces composants, nous pouvons modifier leur API si nécessaire. Nous pouvons également changer les bibliothÚques utilisées en un seul endroit.



Cela s'applique aux deux bibliothÚques de composants telles que l'interface utilisateur et les utilitaires sémantiques. Le moyen le plus simple consiste à réexporter ces composants à partir du module partagé.



Le composant n'a pas besoin de savoir quelle bibliothĂšque particuliĂšre nous utilisons.



//     
import { Button } from 'semantic-ui-react'
import DatePicker from 'react-datepicker'

//       
import { Button, DatePicker } from '@modules/common/components'

      
      





Un composant - un répertoire


Je crée un répertoire de composants pour chaque module de mes applications. Tout d'abord, je crée un composant. Ensuite, s'il existe un besoin de fichiers supplémentaires liés au composant, tels que des styles ou des tests, je crée un répertoire pour le composant et y place tous les fichiers.



Il est recommandé de créer un fichier "index.js" pour réexporter le composant. Cela vous permet de ne pas modifier les chemins d'importation et d'éviter la duplication du nom du composant - "importer le formulaire à partir de 'composants / UserForm / UserForm'". Cependant, vous ne devez pas mettre le code du composant dans le fichier "index.js", car cela rendra impossible la recherche du composant par le nom de l'onglet dans l'éditeur de code.



//        
├── components
    ├── Header.jsx
    ├── Header.scss
    ├── Header.test.jsx
    ├── Footer.jsx
    ├── Footer.scss
    ├── Footer.test.jsx

//      
├── components
    ├── Header
        ├── index.js
        ├── Header.jsx
        ├── Header.scss
        ├── Header.test.jsx
    ├── Footer
        ├── index.js
        ├── Footer.jsx
        ├── Footer.scss
        ├── Footer.test.jsx

      
      





Performance



Optimisation prématurée


Avant de commencer l'optimisation, assurez-vous qu'il existe des raisons à cela. Suivre aveuglément les meilleures pratiques est une perte de temps si cela n'a aucun impact sur l'application.



Bien sĂ»r, vous devez penser Ă  des choses comme l'optimisation, mais votre prĂ©fĂ©rence devrait ĂȘtre de dĂ©velopper des composants lisibles et maintenables. Un code bien Ă©crit est plus facile Ă  amĂ©liorer.



Si vous remarquez un problÚme de performance d'application, mesurez-le et déterminez la cause. Cela n'a aucun sens de réduire le nombre de rendus avec une taille de bundle énorme.



AprÚs avoir identifié les problÚmes, corrigez-les par ordre d'impact sur les performances.



Taille de construction


La quantitĂ© de JavaScript envoyĂ©e au navigateur est un facteur clĂ© des performances de l'application. L'application elle-mĂȘme peut ĂȘtre trĂšs rapide, mais personne ne le saura si vous devez prĂ©charger 4 Mo de JavaScript pour l'exĂ©cuter.



Ne visez pas un seul paquet. Divisez votre application au niveau de l'itinéraire et plus encore. Assurez-vous d'envoyer le minimum de code au navigateur.



Charger en arriĂšre-plan ou lorsque l'utilisateur a l'intention d'obtenir une autre partie de l'application. Si vous cliquez sur un bouton pour lancer un tĂ©lĂ©chargement PDF, vous pouvez commencer Ă  tĂ©lĂ©charger la bibliothĂšque correspondante au moment oĂč vous survolez le bouton.



Re-rendu - rappels, tableaux et objets


Vous devez vous efforcer de réduire le nombre de ré-rendus des composants. Gardez cela à l'esprit, mais gardez également à l'esprit que les rendus inutiles ont rarement un impact significatif sur l'application.



N'envoyez pas de rappels comme accessoires. Avec cette approche, la fonction est recréée à chaque fois, déclenchant un nouveau rendu.



Si vous rencontrez des problÚmes de performances dus à des fermetures, éliminez-les. Mais ne rendez pas votre code moins lisible ou trop détaillé.



Passer des tableaux ou des objets entre explicitement dans la mĂȘme catĂ©gorie de problĂšmes. Ils sont comparĂ©s par rĂ©fĂ©rence, donc ils ne passent pas une vĂ©rification superficielle et dĂ©clenchent un nouveau rendu. Si vous devez transmettre un tableau statique, crĂ©ez-le en tant que constante avant de dĂ©finir le composant. Cela permettra Ă  la mĂȘme instance d'ĂȘtre transmise Ă  chaque fois.



Essai



Test de cliché


Une fois, j'ai rencontré un problÚme intéressant en faisant des tests d'instantané: comparer «new Date ()» sans argument à la date actuelle retournait toujours «false».



En outre, les instantanés entraßnent des échecs d'assemblage uniquement lorsque le composant est modifié. Un flux de travail typique est le suivant: apportez des modifications au composant, échouez au test, mettez à jour l'instantané et continuez.



Il est important de comprendre que les instantanés ne remplacent pas les tests au niveau des composants. Personnellement, je n'utilise plus ce type de test.



Test du rendu correct


Le but principal des tests est de confirmer que le composant fonctionne comme prévu. Assurez-vous que le composant renvoie le balisage correct avec les accessoires par défaut et passés.



Assurez-vous également que la fonction renvoie toujours des résultats corrects pour des entrées spécifiques. Vérifiez que tout ce dont vous avez besoin s'affiche correctement à l'écran.



Tester l'état et les événements


Un composant avec état change généralement en réponse à un événement. Créez une simulation d'événement et vérifiez que le composant y répond correctement.



Assurez-vous que les gestionnaires sont appelés et que les arguments corrects sont passés. Vérifiez le réglage correct de l'état interne.



Test des cas de pointe


AprÚs avoir couvert le code avec des tests de base, ajoutez des tests pour vérifier les cas particuliers.



Cela peut signifier passer un tableau vide pour s'assurer que l'index n'est pas accessible sans vĂ©rification. Cela peut Ă©galement signifier appeler une erreur dans un composant (par exemple, dans une requĂȘte API) pour vĂ©rifier si elle a Ă©tĂ© gĂ©rĂ©e correctement.



Test d'intégration


Le test d'intégration consiste à tester une page entiÚre ou un grand composant. Ce type de test consiste à tester les performances d'une certaine abstraction. Il fournit un résultat plus convaincant que l'application fonctionne comme prévu.



Les composants individuels peuvent réussir les tests unitaires, mais les interactions entre eux peuvent entraßner des problÚmes.



Stylisation



CSS vers JS


C'est une question trÚs controversée. Personnellement, je préfÚre utiliser des bibliothÚques comme Styled Components ou Emotion, qui vous permettent d'écrire des styles en JavaScript. Un fichier de moins. Ne pensez pas à des choses comme les noms de classe.



Le bloc de construction de React est un composant, donc la technique CSS-in-JS, ou plus précisément tout-en-JS, est à mon avis la technique préférée.



Veuillez noter que les autres approches de style (SCSS, modules CSS, bibliothĂšques avec des styles comme Tailwind) ne sont pas fausses, mais je recommande toujours d'utiliser CSS-in-JS.



Composants stylisés


Habituellement, j'essaie de conserver les composants stylisĂ©s et le composant qui les utilise dans le mĂȘme fichier.



Cependant, lorsqu'il y a beaucoup de composants stylés, il est logique de les déplacer dans un fichier séparé. J'ai vu cette approche utilisée dans certains projets open source comme Spectrum.



Réception des données



BibliothÚques pour travailler avec des données


React ne fournit aucun outil spécial pour obtenir ou mettre à jour des données. Chaque équipe crée sa propre implémentation, comprenant généralement un service pour les fonctions asynchrones qui interagissent avec l'API.



Adopter cette approche signifie qu'il est de notre responsabilité de suivre l'état du téléchargement et de gérer les erreurs HTTP. Cela conduit à la verbosité et à beaucoup de code standard.



Au lieu de cela, il est préférable d'utiliser des bibliothÚques telles que React Query et SWR . Ils font des interactions serveur une partie organique du cycle de vie des composants de maniÚre idiomatique - à l'aide de hooks.



Ils ont un support intégré pour la mise en cache, la gestion de l'état de charge et la gestion des erreurs. Ils éliminent également le besoin de bibliothÚques de gestion d'état pour gérer ces données.



Merci pour votre attention et un bon début de semaine de travail.



All Articles