image du logo de React

Composants Composés, La nouvelle Méthode d’écrire des Composants React :

Il y a peu, j'ai découvert les composants composés en typescript et je ne peux plus m’en passer. Souvent peu référencé, peu connu du vaste monde des développeurs, je vous propose une petite introduction au monde des “composés”.

Qu’est-ce que c’est ?

Un composant composé se trouve sous cette forme :

const Container = () => (
 <MyComponent>
  <MyComponent.Header> Un Header </MyComponent.Header>
  <MyComponent.Body> Un Body </MyComponent.Body>
  <MyComponent.Footer> Un Footer </MyComponent.Footer>
 </MyComponent>
)
  • MyComponent: est le composant React parent de tous les composés
  • Header, Body, Footer : sont les composants composés du parent, ils ne peuvent pas être utilisés dans l’application en dehors du parent.

Le parent : MyComponent :

Le parent des composés s’écrit de la manière suivante :

import React from 'react'
import { MyComponentContextProvider } from './card-context'
import Body from './body'
 ...
interface MyComponentCompounds {
 Body: Body
 ...
}
interface MyComponentType {
 props: ...
 children: React.ReactNode
}
const MyComponent React.FC<MyComponentType> & MyComponentCompounds = ({props,children}) => {
 const context = useMemo(() => ({ ... }), [...])
 return (
   <MyComponentContextProvider value={context}>
    {children}
   </MyComponentContextProvider>
 )
}
MyComponent.Body = Body
 ...
export default MyComponent

Il est l’équivalent d’un contenaire. On peut en profiter pour lui donner le style des contours ainsi que le style de placement associé au composant global. Le parent peut avoir un Context-Provider qui permettra aux enfants de réutiliser certaines valeurs du parent à l’aide des hooks de react.

Exemple de code pour la création du provider et du contexte hooks:

import { createContext, useContext } from 'react'
type myComponentContextType = {
 value?: ...
}
const MyComponentContext = createContext<myComponentContextType>({})
export const MyComponentContextProvider = MyComponentContext.Provider
export function useMyComponentContext() {
 const context = useContext(MyComponentContext)
 if (!context) {
  throw new Error('Blablabla erreur blablabla')
 }
 return context
}

Les composés : Body

Le composant composé aura une structure semblable à un composant React normale, mais il doit impérativement rester petit, comme le décrit l’exemple suivant :

import React from 'react'
interface BodyPropsType {
 props: ...
}
const Body = ({ props }: BodyPropsType) => {
 const {value} = useMyComponentContext()
 return (
  <div>
   {...}
  </div>
 )
}
export default Body

Il sera capable de réutiliser des valeurs stockées dans le contexte. Il gère son propre style et ses propres fonctionnalités.

Si besoin, on peut chainer les composants composés comme dans cet exemple :

const Container = () => (
 <MyComponent>
  <MyComponent.Header> Un Header </MyComponent.Header>
  <MyComponent.Body> 
   <MyComponent.Body.Title>Un Titre </MyComponent.Body.Title>
   <MyComponent.Body.SubTitle>Un ...</MyComponent.Body.SubTitle>
  </MyComponent.Body>
  <MyComponent.Footer> Un Footer </MyComponent.Footer>
 </MyComponent>
)

Title et Subtitle sont des composés du composant Body qui lui-même est un composé du composant MyComponent.

Les Avantages Des Composés en Type-Script :

Les avantages des composants composés sont les suivants :

  • Avec TypeScript l’auto-complétion ! Cela peut paraitre tout bête, mais lors de l’intégration de composants ça donne beaucoup de clarté de savoir ce qui peut être fait par notre composant.
  • La clarté dans la gestion des props: chaque Composé gère ses propres props, cela évite au parent de porter toutes les props et évite le chainage de props
const Container = () => (
 <MyComponent {...propsComponent}>
  <MyComponent.Body {...propsBody}> Un Body </MyComponent.Body>
 </MyComponent>
)
VS
const Container = () => (
 <MyComponent propsComponent={propsComponent} propsBody={propsBody} messageBody='Un Body' />
)
  • Les performances : gros sujets des développeurs front, la performance de leur code et notamment la chasse au re-render inutiles. Isoler certaines props à leur composé au lieu de passer par leur parent, évite les re-render intempestifs.
    En d’autres termes, Si Body a besoin de se re-render pour un changement d’état ou de props quelconque, Header n’en a peut-être pas besoin.
    Je vous conseille d’utiliser : why-did-you-render pour comprendre le re-render de certains composants.
  • La scalabilité: deuxième gros sujet connu par tous:
    comment faire du code standard réutilisable ?
    Le secret les composés.
    En effet plus notre code est découpé en petite partie,
    plus notre code sera standard et “propre”,
    plus il sera facile de le réintégrer dans des composants et ainsi éviter de coder deux fois la même choses.
  • Le petit Bonus: la maintenabilité ! A qui ça n’est pas arrivé de devoir retoucher à un composant React de 300 lignes incompréhensible ? Lorsque l’on code, il faut bien garder en tête que notre code à une date de péremption. Tout ce qui écrit aujourd’hui et qui est considéré comme acceptable ne le sera peut être plus dans 1 an. Construire des composants “léger”, “petits”, c’est construire des composants claires et maintenables pour l’avenir!

[keep it simple, keep it smart]