API React utiles pour créer des composants flexibles avec TypeScript
Publié: 2022-03-10 Avez-vous déjà utilisé React.createElement
directement ? Qu'en est-il de React.cloneElement
? React est plus que simplement transformer votre JSX en HTML. Bien plus, et pour vous aider à améliorer vos connaissances sur les API moins connues (mais très utiles) fournies avec la bibliothèque React. Nous allons passer en revue quelques-uns d'entre eux et certains de leurs cas d'utilisation qui peuvent considérablement améliorer l'intégration et l'utilité de vos composants.
Dans cet article, nous passerons en revue quelques API React utiles qui ne sont pas aussi connues mais extrêmement utiles pour les développeurs Web. Les lecteurs doivent être expérimentés avec la syntaxe React et JSX, la connaissance de Typescript est utile mais pas nécessaire. Les lecteurs repartiront avec tout ce qu'ils doivent savoir afin d'améliorer considérablement les composants React lors de leur utilisation dans les applications React.
React.cloneElement
La plupart des développeurs n'ont peut-être jamais entendu parler de cloneElement
ou ne l'ont jamais utilisé. Il a été introduit relativement récemment pour remplacer la fonction maintenant obsolète cloneWithProps
. cloneElement
clone un élément, il vous permet également de fusionner de nouveaux accessoires avec l'élément existant, en les modifiant ou en les remplaçant comme bon vous semble. Cela ouvre des options extrêmement puissantes pour créer des API de classe mondiale pour les composants fonctionnels. Jetez un oeil à la signature.
function cloneElement( element, props?, ...children)
Voici la version condensée de Typescript :
function cloneElement( element: ReactElement, props?: HTMLAttributes, ...children: ReactNode[]): ReactElement
Vous pouvez prendre un élément, le modifier, voire remplacer ses enfants, puis le renvoyer en tant que nouvel élément. Jetez un oeil à l'exemple suivant. Disons que nous voulons créer un composant TabBar de liens. Cela pourrait ressembler à ceci.
export interface ITabbarProps { links: {title: string, url: string}[] } export default function Tabbar(props: ITabbarProps) { return ( <> {props.links.map((e, i) => <a key={i} href={e.url}>{e.title}</a> )} </> ) }
La TabBar est une liste de liens, mais nous avons besoin d'un moyen de définir deux éléments de données, le titre du lien et l'URL. Nous voudrons donc qu'une structure de données soit transmise avec ces informations. Donc, notre développeur ferait notre composant comme ça.
function App() { return ( <Tabbar links={[ {title: 'First', url: '/first'}, {title: 'Second', url: '/second'}] } /> ) }
C'est très bien, mais que se passe-t-il si l'utilisateur souhaite afficher des éléments de button
au lieu d' a
élément ? Eh bien, nous pourrions ajouter une autre propriété qui indique au composant quel type d'élément rendre.
Mais vous pouvez voir à quel point cela deviendra rapidement difficile à manier, nous aurions besoin de prendre en charge de plus en plus de propriétés pour gérer divers cas d'utilisation et cas extrêmes pour une flexibilité maximale.
Voici une meilleure façon, en utilisant React.cloneElement
.
Nous allons commencer par modifier notre interface pour référencer le type ReactNode
. Il s'agit d'un type générique qui englobe tout ce que React peut rendre, généralement des éléments JSX, mais peut également être des chaînes et même null
. Ceci est utile pour vous indiquer de vouloir accepter les composants React ou JSX comme arguments en ligne.
export interface ITabbarProps { links: ReactNode[] }
Maintenant, nous demandons à l'utilisateur de nous donner des éléments React, et nous les rendrons comme nous le voulons.
function Tabbar(props: ITabbarProps) { return ( <> {props.links.map((e, i) => e // simply return the element itself )} </> ) }
Ceci est parfaitement valide et rendrait nos éléments. Mais nous oublions quelques choses. D'une part, key
! Nous voulons ajouter des clés pour que React puisse rendre nos listes efficacement. Nous souhaitons également modifier nos éléments pour effectuer les transformations nécessaires afin qu'ils s'intègrent dans notre style, tels que className
, etc.
Nous pouvons le faire avec React.cloneElement
et une autre fonction React.isValidElement
pour vérifier que l'argument est conforme à ce que nous attendons !
React.isValidElement
Cette fonction renvoie true
si un élément est un élément React valide et que React peut le rendre. Voici un exemple de modification des éléments de l'exemple précédent.
function Tabbar(props: ITabbarProps) { return ( <> {props.links.map((e, i) => isValidElement(e) && cloneElement(e, {key: `${i}`, className: 'bold'}) )} </> ) }
Ici, nous ajoutons un accessoire clé à chaque élément que nous transmettons et rendons chaque lien en gras en même temps ! Nous pouvons maintenant accepter des éléments React arbitraires comme accessoires comme ceci :
function App() { return ( <Tabbar links={[ <a href='/first'>First</a>, <button type='button'>Second</button> ]} /> ) }
Nous pouvons remplacer n'importe lequel des accessoires définis sur un élément et accepter facilement différents types d'éléments, ce qui rend notre composant beaucoup plus flexible et facile à utiliser.
"
L'avantage ici est que si nous voulions définir un gestionnaire onClick
personnalisé sur notre bouton, nous pourrions le faire. Accepter les éléments React eux-mêmes comme arguments est un moyen puissant de donner de la flexibilité à la conception de vos composants.
useState
Setter Fonction
Utilisez des crochets ! Le hook useState
est extrêmement utile et constitue une API fantastique pour créer rapidement un état dans vos composants, comme ceci :
const [myValue, setMyValue] = useState()
En raison de l'exécution de JavaScript, il peut y avoir quelques ratés. Rappelez-vous les fermetures ?
Dans certaines situations, une variable peut ne pas avoir la valeur correcte en raison du contexte dans lequel elle se trouve, comme dans les boucles for courantes ou les événements asynchrones. Cela est dû à la portée lexicale. Lorsqu'une nouvelle fonction est créée, la portée lexicale est préservée. Puisqu'il n'y a pas de nouvelle fonction, la portée lexicale de newVal
n'est pas préservée, et donc la valeur est en fait déréférencée au moment où elle est utilisée.
setTimeout(() => { setMyValue(newVal) // this will not work }, 1000)
Ce que vous devrez faire, c'est utiliser le setter en tant que fonction. En créant une nouvelle fonction, la référence des variables est conservée dans la portée lexicale et le currentVal est transmis par le React useState Hook lui-même.
setTimeout(() => { setMyValue((currentVal) => { return newVal }) }, 1000)
Cela garantira que votre valeur est correctement mise à jour car la fonction setter est appelée dans le bon contexte. Ce que React fait ici, c'est appeler votre fonction dans le bon contexte pour qu'une mise à jour de l'état de React se produise. Cela peut également être utilisé dans d'autres situations où il est utile d'agir sur la valeur actuelle, React appelle votre fonction avec le premier argument comme valeur actuelle.
Remarque : Pour une lecture supplémentaire sur le sujet de l'asynchronisme et des fermetures, je vous recommande de lire « useState
Lazy Initialization And Function Updates » par Kent C. Dodds.
Fonctions en ligne JSX
Voici une démo Codepen d'une fonction en ligne JSX :
Pas exactement une API React par exemple.
JSX prend en charge les fonctions en ligne et cela peut être très utile pour déclarer une logique simple avec des variables en ligne, tant qu'il renvoie un élément JSX.
"
Voici un exemple :
function App() { return ( <> {(() => { const darkMode = isDarkMode() if (darkMode) { return ( <div className='dark-mode'></div> ) } else { return ( <div className='light-mode'></div> ) // we can declare JSX anywhere! } })()} // don't forget to call the function! </> ) }
Ici, nous déclarons du code à l'intérieur de JSX, nous pouvons exécuter du code arbitraire et tout ce que nous avons à faire est de renvoyer une fonction JSX à rendre.
Nous pouvons le rendre conditionnel ou simplement exécuter une logique. Prenez note des parenthèses entourant la fonction en ligne. Aussi particulièrement ici où nous appelons cette fonction, nous pourrions même passer un argument dans cette fonction à partir du contexte environnant si nous le voulions !
})()}
Cela peut être utile dans les situations où vous souhaitez agir sur une structure de données de collection d'une manière plus complexe qu'un .map
standard ne le permet à l'intérieur d'un élément JSX.
function App() { return ( <> {(() => { let str = '' for (let i = 0; i < 10; i++) { str += i } return (<p>{str}</p>) })()} </> ) }
Ici, nous pouvons exécuter du code pour parcourir un ensemble de nombres, puis les afficher en ligne. Si vous utilisez un générateur de site statique tel que Gatsby, cette étape sera également pré-calculée.
component extends type
Immensément utile pour créer des composants compatibles avec la saisie semi-automatique, cette fonctionnalité vous permet de créer des composants qui étendent des HTMLElements
existants ou d'autres composants. Surtout utile pour taper correctement une interface d'éléments dans Typescript mais l'application réelle est la même pour JavaScript.
Voici un exemple simple, disons que nous voulons remplacer une ou deux propriétés d'un élément de button
, mais donnons toujours aux développeurs la possibilité d'ajouter d'autres propriétés au bouton. Comme le paramètre type='button'
ou type='submit'
. Nous ne voulons évidemment pas recréer l'intégralité de l'élément bouton, nous voulons simplement étendre ses propriétés existantes, et peut-être ajouter un autre accessoire.
import React, { ButtonHTMLAttributes } from 'react'
Nous importons d'abord React et la classe ButtonHTMLAttributes
, un type qui englobe les props d'un HTMLButtonElement
. Vous pouvez lire le code source de ce type d'interface ici :
Et vous pouvez voir que l'équipe React a réimplémenté toutes les API du Web dans TypeScript afin qu'elles puissent être vérifiées.
Ensuite, nous déclarons notre interface ainsi, en ajoutant notre propriété status
.
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { status?: 'primary' | 'info' | 'danger' }
Et enfin, nous faisons quelques choses. Nous utilisons la déstructuration ES6 pour extraire les accessoires qui nous intéressent ( status
et children
) et déclarer toutes les autres propriétés comme rest
. Et dans notre sortie JSX, nous renvoyons un élément de bouton, avec une structuration ES6 pour ajouter des propriétés supplémentaires à cet élément.
function Button(props: ButtonProps) { const { status, children, ...rest } = props // rest has any other props return ( <button className={`${status}`} {...rest} // we pass the rest of the props back into the element > {children} </button> ) }
Alors maintenant, un développeur peut ajouter un accessoire de type
ou tout autre accessoire qu'un bouton aurait généralement. Nous avons donné un accessoire supplémentaire que nous avons utilisé dans le className
pour définir le style du bouton.
Voici l'exemple complet :
import React, { ButtonHTMLAttributes } from 'react' export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { status?: 'primary' | 'info' | 'danger' } export default function Button(props: ButtonProps) { const { status, children, ...rest } = props return ( <button className={`${status}`} {...rest} > {children} </button> ) }
Cela constitue un excellent moyen de créer des composants internes réutilisables conformes à vos directives de style sans reconstruire des éléments HTML entiers ! Vous pouvez simplement remplacer des accessoires entiers, tels que la définition de className
en fonction du statut ou autoriser également la transmission de noms de classe supplémentaires.
import React, { ButtonHTMLAttributes } from 'react' export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { status?: 'primary' | 'info' | 'danger' } export default function Button(props: ButtonProps) { const { status, children, className, ...rest } = props return ( <button className={`${status || ''} ${className || ''}`} {...rest} > {children} </button> ) }
Ici, nous prenons l'accessoire className
transmis à notre élément Button et l'insérons à nouveau, avec un contrôle de sécurité dans le cas où l'accessoire est undefined
.
Conclusion
React est une bibliothèque extrêmement puissante, et il y a une bonne raison pour laquelle elle a rapidement gagné en popularité. Il vous offre un excellent ensemble d'outils pour créer des applications Web performantes et faciles à entretenir. C'est extrêmement flexible et pourtant très strict en même temps, ce qui peut être incroyablement utile si vous savez comment l'utiliser. Ce ne sont là que quelques API remarquables et largement ignorées. Essayez-les dans votre prochain projet !
Pour en savoir plus sur les dernières API React, les hooks, je vous recommande de lire useHooks(). Le Typescript Cheatsheet contient également d'excellentes informations pour React et Typescript Hooks.