Penser en React
Introduction
Pour bien débuter une application en React, il est recommandé de débuter par une maquette froide de l'application. Cela permet de visualiser l'ensemble des composants nécessaires à la réalisation de l'application.
Étape 1 : Découper en composants
Pour découper l'application en composants, il est recommandé de suivre les étapes suivantes :
- Identifier les composants réutilisables
- Découper les composants en sous-composants
- Définir la hiérarchie des composants
- ProduitTableFiltrable (bleu) contient l'application au complet.
- BarreRecherche (vert) reçoit l'entrée de l'utilisateur.
- ProduitTable (jaune) affiche et filtre la liste selon l'entrée de l'utilisateur.
- ProduitCategorieRangee (rose) affiche l'entête par catégorie.
- ProduitRangee (brun) affiche une rangée pour un produit.
Ces composants peuvent être représentés en une hiérarchie de composants :
Étape 2 : Construire une version statique de l'application
Passage de données par props
Les données sont passées de haut en bas dans React. Cela signifie que les données sont passées du composant parent au composant enfant par le biais de props
.
Commencez pas le composant de produit le plus bas de la hiérarchie, ProduitRangee
. Ce composant reçoit des données sous forme de props
et les affiche.
import { Produit } from '../models/Produit.model';
interface ProduitRangeeProps {
produit: Produit;
}
export default function ProduitRangee(props: ProduitRangeeProps) {
const nom = props.produit.en_inventaire ? (
props.produit.nom
) : (
<span style={{ color: 'red' }}>{props.produit.nom}</span>
);
return (
<tr>
<td>{nom}</td>
<td>{props.produit.prix}</td>
</tr>
);
}
Ensuite, construisez le composant ProduitCategorieRangee
qui reçoit la catégorie sous forme de props
et les affiche.
interface ProduitCategorieRangeeProps {
categorie: string;
}
export default function ProduitCategorieRangee(
props: ProduitCategorieRangeeProps
) {
return (
<tr>
<th colSpan={2}>{props.categorie}</th>
</tr>
);
}
Enfin, construisez le composant ProduitTable
qui reçoit les données sous forme de props
et les affiche.
import { Produit } from '../models/Produit.model';
import ProduitCategorieRangee from './ProduitCategorieRangee';
import ProduitRangee from './ProduitRangee';
interface ProduitTableProps {
produits: Produit[];
}
export default function ProduitTable(props: ProduitTableProps) {
const produits = props.produits;
const rangees: JSX.Element[] = [];
let derniereCategorie: string | null = null;
produits.forEach((produit) => {
if (produit.categorie !== derniereCategorie) {
rangees.push(
<ProduitCategorieRangee
categorie={produit.categorie}
key={produit.categorie}
/>
);
}
rangees.push(<ProduitRangee produit={produit} key={produit.nom} />);
derniereCategorie = produit.categorie;
});
return (
<table>
<thead>
<tr>
<th>Nom</th>
<th>Prix</th>
</tr>
</thead>
<tbody>{rangees}</tbody>
</table>
);
}
Étape 3 : Identifier l'état minimal de l'application
Il faut maintenant identifier ce qui doit être conservé dans un état (state) VS ce qui est statique ou dérivé d'un état.
Quelles sont les données de notre application?
- La liste des produits originale
- La catégorie des produits
- Le texte de recherche
- L'état de la case à cocher
- Les produits filtrés
Quelles sont les données qui changent au cours du temps?
- Le texte de recherche
- L'état de la case à cocher
- Les produits filtrés
Quelles sont les données qui peuvent être dérivées?
- Les produits filtrés
Donc, quelles sont les états minimaux de notre application?
- Le texte de recherche
- L'état de la case à cocher
Étape 4 : Identifier où l'état doit vivre
Le principe de levée de l'état
L'état doit être levé au composant parent le plus proche qui a besoin d'y accéder.
Dans notre cas, le texte de recherche et l'état de la case à cocher seront utilisés par deux composants, BarreRecherche
et ProduitTable
. Donc, l'état doit être levé au composant parent commun, ProduitTableFiltrable
.
import { Produit } from '../models/Produit.model';
import BarreRecherche from './BarreRecherche';
import ProduitTable from './ProduitTable';
import { useState } from 'react';
interface ProduitTableFiltrableProps {
produits: Produit[];
}
export default function ProduitTableFiltrable(
props: ProduitTableFiltrableProps
) {
const [filtreTexte, setFiltreTexte] = useState('');
const [filtreInventaireSeulement, setFiltreInventaireSeulement] =
useState(false);
const gererFiltreTexteChange = (valeur: string) => {
setFiltreTexte(valeur);
};
const gererFiltreInventaireSeulementChange = (valeur: boolean) => {
setFiltreInventaireSeulement(valeur);
};
return (
<div>
<BarreRecherche
texte={filtreTexte}
seulementEnInventaire={filtreInventaireSeulement}
onTexteChange={gererFiltreTexteChange}
onSeulementEnInventaireChange={gererFiltreInventaireSeulementChange}
/>
<ProduitTable
produits={props.produits}
texte={filtreTexte}
seulementEnInventaire={filtreInventaireSeulement}
/>
</div>
);
}
Étape 5 : Ajouter des interactions
Maintenant que l'état est levé au composant parent commun, nous pouvons ajouter des interactions à notre application.
Passage de données par props
Les fonctions de rappel sont passées de haut en bas dans React. Cela signifie que les fonctions de rappel sont passées du composant parent au composant enfant par le biais de props
.
Ajoutez une fonction de rappel pour gérer le changement de texte de recherche et de la case à cocher dans BarreRecherche
.
interface BarreRechercheProps {
texte: string;
seulementEnInventaire: boolean;
onTexteChange: (texte: string) => void;
onSeulementEnInventaireChange: (seulementEnInventaire: boolean) => void;
}
export default function BarreRecherche(props: BarreRechercheProps) {
function handleTexteChange(e: React.ChangeEvent<HTMLInputElement>) {
props.onTexteChange(e.target.value);
}
function handleSeulementEnInventaireChange(
e: React.ChangeEvent<HTMLInputElement>
) {
props.onSeulementEnInventaireChange(e.target.checked);
}
return (
<form>
<input
type="text"
placeholder="Recherche..."
value={props.texte}
onChange={handleTexteChange}
/>
<label>
<input
type="checkbox"
checked={props.seulementEnInventaire}
onChange={handleSeulementEnInventaireChange}
/>{' '}
Seulement les produits en inventaire
</label>
</form>
);
}
import { Produit } from '../models/Produit.model';
import ProduitCategorieRangee from './ProduitCategorieRangee';
import ProduitRangee from './ProduitRangee';
interface ProduitTableProps {
produits: Produit[];
texte: string;
seulementEnInventaire: boolean;
}
export default function ProduitTable(props: ProduitTableProps) {
const produits = props.produits;
const rangees: JSX.Element[] = [];
let derniereCategorie: string | null = null;
produits.forEach((produit) => {
if (props.texte !== '' && !produit.nom.includes(props.texte)) {
return;
}
if (props.seulementEnInventaire && !produit.en_inventaire) {
return;
}
if (produit.categorie !== derniereCategorie) {
rangees.push(
<ProduitCategorieRangee
categorie={produit.categorie}
key={produit.categorie}
/>
);
}
rangees.push(<ProduitRangee produit={produit} key={produit.nom} />);
derniereCategorie = produit.categorie;
});
return (
<table>
<thead>
<tr>
<th>Nom</th>
<th>Prix</th>
</tr>
</thead>
<tbody>{rangees}</tbody>
</table>
);
}