Aller au contenu

React • Créer des composants

Dans cet article, nous allons reprendre le projet créé dans l’article précédent et l’améliorer en :

  • créant des nouveaux composants ;
  • utiliser TypeScript pour typer nos composants.
  • RépertoireREACT-PROJECT
    • Répertoirenode_modules/
    • Répertoirepublic/
      • vite.svg
    • Répertoiresrc/
      • Répertoireassets/
        • react.svg
      • App.css
      • App.tsx
      • index.css
      • main.tsx
      • vite-end.d.ts
    • .eslintrc.cjs
    • .gitignore
    • index.html
    • package-lock.json
    • package.json
    • README.md
    • tsconfig.app.json
    • tsconfig.json
    • tsconfig.node.json
    • vite.config.ts
App.tsx
import { useState } from "react";
import reactLogo from "./assets/react.svg";
import viteLogo from "/vite.svg";
import "./App.css";
function App() {
const [count, setCount] = useState(0);
return (
<>
<div>
<a href="https://vitejs.dev" target="_blank">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
<p>
Edit <code>src/App.tsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">
Click on the Vite and React logos to learn more
</p>
</>
);
}
export default App

Dans le composant App nous avons la possibilité d’extraire le lien <a> et son image <img> dans un nouveau composant.

Créons un nouveau fichier ImageLink.tsx.

  • RépertoireREACT-PROJECT
    • Répertoirenode_modules/
    • Répertoirepublic/
      • vite.svg
    • Répertoiresrc/
      • Répertoireassets/
        • react.svg
      • App.css
      • App.tsx
      • ImageLink.tsx
      • index.css
      • main.tsx
      • vite-end.d.ts
    • .eslintrc.cjs
    • .gitignore
    • index.html
    • package-lock.json
    • package.json
    • README.md
    • tsconfig.app.json
    • tsconfig.json
    • tsconfig.node.json
    • vite.config.ts

En s’inspirant du composant App, commençons par écrire le code suivant.

ImageLink.tsx
function ImageLink() {
return (
<></>
);
}
export default ImageLink

Notre composant va avoir besoin de paramètres :

  • l’URL du lien ;
  • la source de l’image ;
  • le texte alternatif de l’image ;
  • la classe CSS associée à l’image.

Cette liste de paramètre s’appelle Props. Les props doivent être regroupées dans un objet.

ImageLink.tsx
function ImageLink() {
function ImageLink({href, imgSrc, imgAlt, imgClassName}) {
return (
<></>
);
}
export default ImageLink;

Remettons en place les balises <a> et <img> dans notre composant et utilisons les paramètres.

ImageLink.tsx
function ImageLink({href, imgSrc, imgAlt, imgClassName}) {
return (
<></>
<a href={href} target="_blank">
<img src={imgSrc} className={imgClassName} alt={imgAlt} />
</a>
);
}
export default ImageLink

Nous pouvons maintenant utiliser notre nouveau composant dans App.

App.tsx
import { useState } from "react";
import reactLogo from "./assets/react.svg";
import viteLogo from "/vite.svg";
import "./App.css";
import ImageLink from "./ImageLink";
function App() {
const [count, setCount] = useState(0);
return (
<>
<div>
<a href="https://vitejs.dev" target="_blank">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
<ImageLink
href="https://vitejs.dev"
imgAlt="Vite logo"
imgClassName="logo"
imgSrc={viteLogo}
/>
<ImageLink
href="https://react.dev"
imgAlt="React logo"
imgClassName="logo react"
imgSrc={reactLogo}
/>
</div>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
<p>
Edit <code>src/App.tsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">
Click on the Vite and React logos to learn more
</p>
</>
);
}
export default App

Nous avons remplacé les deux <img> par notre nouveau composant. Sur le navigateur, la page fonctionne toujours de la même façon.

Néanmoins, si nous revenons sur le composant <ImageLink> l’éditeur de code nous signale des erreurs sur les paramètres de notre fonction.

Binding element ‘href’ implicitly has an ‘any’ type.ts(7031)

En effet, nous avons développé en pur Javascript, sans rien typer. Nous allons maintenant voir comment modifier notre composant afin de profiter de TypeScript.

Nous allons d’abord créer un type pour les propriétés de notre composant ImageLink.

ImageLink.tsx
type Props = {
href: string,
imgSrc: string,
imgAlt: string,
imgClassName: string,
};
function ImageLink({href, imgSrc, imgAlt, imgClassName}: Props) {
return (
<a href={href} target="_blank">
<img src={imgSrc} className={imgClassName} alt={imgAlt} />
</a>
);
}
export default ImageLink;

Comment appliquer ce type à notre fonction ?

Première méthode : appliquer le type directement à l’objet en paramètre de la fonction.

ImageLink.tsx
type Props = {
href: string,
imgSrc: string,
imgAlt: string,
imgClassName: string,
};
function ImageLink({href, imgSrc, imgAlt, imgClassName}: Props) {
return (
<a href={href} target="_blank">
<img src={imgSrc} className={imgClassName} alt={imgAlt} />
</a>
);
}
export default ImageLink;

Seconde méthode : profiter des types proposés par React.

ImageLink.tsx
import { FC } from "react";
type Props = {
href: string,
imgSrc: string,
imgAlt: string,
imgClassName: string,
};
function ImageLink({href, imgSrc, imgAlt, imgClassName}: Props) {
const ImageLink: FC<Props> = ({ href, imgSrc, imgAlt, imgClassName }) => {
return (
<a href={href} target="_blank">
<img src={imgSrc} className={imgClassName} alt={imgAlt} />
</a>
);
}
export default ImageLink;
  1. La fonction ImageLink() a été convertie en Arrow function ;
  2. Le type FC (Function Component) a été appliqué à la fonction ImageLink() ;
  3. Le type FC peut recevoir le type d’argument, le type Props a été appliqué FC<Props>.
(alias) type FC<P = {}> = FunctionComponent<P>
Represents the type of a function component. Can optionally receive a type argument that represents the props the component receives.

Grace au type Props, nous avons défini toutes les propriétés attendues, ainsi que leur type.

Imaginons maintenant que nous souhaitions que le paramètre imgClassName soit optionnel. Comment pouvons-nous faire ?

ImageLink.tsx
import { FC } from "react";
type Props = {
href: string,
imgSrc: string,
imgAlt: string,
imgClassName?: string,
};
function ImageLink({href, imgSrc, imgAlt, imgClassName}: Props) {
const ImageLink: FC<Props> = ({ href, imgSrc, imgAlt, imgClassName }) => {
return (
<a href={href} target="_blank">
<img src={imgSrc} className={imgClassName} alt={imgAlt} />
</a>
);
}
export default ImageLink;

Pour rendre un paramètre optionnel, il suffit d’ajouter ? à son type. Ainsi en déclarant imgClassName?: string, imgClassName peut être undefined.

Dans le composant App, nous souhaitons maintenant remplacer le bouton par un nouveau composant permettant d’incrémenter et de décrémenter le compteur.

Créons un nouveau fichier PlusMinusButton.tsx.

  • RépertoireREACT-PROJECT
    • Répertoirenode_modules/
    • Répertoirepublic/
      • vite.svg
    • Répertoiresrc/
      • Répertoireassets/
        • react.svg
      • App.css
      • App.tsx
      • ImageLink.tsx
      • index.css
      • main.tsx
      • PlusMinusButton.tsx
      • vite-end.d.ts
    • .eslintrc.cjs
    • .gitignore
    • index.html
    • package-lock.json
    • package.json
    • README.md
    • tsconfig.app.json
    • tsconfig.json
    • tsconfig.node.json
    • vite.config.ts
PlusMinusButton.tsx
import { FC } from "react";
type Props = {}
const PlusMinusButton: FC<Props> = () => {
return (
<div>
<button>+</button>
<button>-</button>
</div>
);
}
export default PlusMinusButton;

Notre nouveau composant contient deux <button>. Comment pouvons-nous faire pour indiquer au composant parent que le bouton + ou le bouton - a été cliqué ?

Prenons l’exemple de l’événement onClick du <button> dans le composant App :

<button onClick={() => setCount((count) => count + 1)}>

Une fonction est attachée à onClick. Cette fonction appelle setCount() lorsque l’événement click du <button> est levé. Nous pouvons reproduire le même comportement dans notre composant PlusMinusButton.

PlusMinusButton.tsx
import { FC } from "react";
type Props = {
decrement: () => void,
increment: () => void,
}
const PlusMinusButton: FC<Props> = ({ decrement, increment }) => {
return (
<div>
<button>+</button>
<button onClick={() => increment()}>+</button>
<button>-</button>
<button onClick={() => decrement()}>-</button>
</div>
);
}
export default PlusMinusButton;

Les fonctions decrement() et increment() ont été définies en tant que Props de notre composant. Le type () => void signifie : fonction sans paramètre qui ne retourne rien (void).

La fonction decrement() est appelée au click du <button> -. La fonction increment() est appelée au click du <button> +.

Nous pouvons maintenant utiliser notre nouveau composant dans App.

App.tsx
import { useState } from "react";
import reactLogo from "./assets/react.svg";
import viteLogo from "/vite.svg";
import "./App.css";
import ImageLink from "./ImageLink";
import PlusMinusButton from "./PlusMinusButton";
function App() {
const [count, setCount] = useState(0);
return (
<>
<div>
<a href="https://vitejs.dev" target="_blank">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
<ImageLink
href="https://vitejs.dev"
imgAlt="Vite logo"
imgClassName="logo"
imgSrc={viteLogo}
/>
<ImageLink
href="https://react.dev"
imgAlt="React logo"
imgClassName="logo react"
imgSrc={reactLogo}
/>
</div>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
<PlusMinusButton
decrement={() => setCount((count) => count - 1)}
increment={() => setCount((count) => count + 1)}
/>
<span>count is {count}</span>
<p>
Edit <code>src/App.tsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">
Click on the Vite and React logos to learn more
</p>
</>
);
}
export default App;

Et voilà ! Nous avons mis en place un nouveau composant nous permettant de décrémenter et d’incrémenter notre compteur.

Dans cet article, nous avons vu comment créer des composants avec des paramètres. Ces paramètres s’appellent Props. Avec l’aide de TypeScript, nous avons correctement typé ces Props.

Les Props du composant ImageLink ont une influence sur son rendu. Ainsi le logo Vite et le logo React ont un rendu différent, bien qu’il s’agisse du même composant.

Le composant PlusMinusButton a des Props de type function. Le composant parent App peut ainsi « réagir » lorsqu’une des fonctions est appelée. Mais le rendu de PlusMinusButton ne change pas.

Bien entendu, un composant peut avoir à la fois des Props de type string, number ou array et des Props de type function.