paint-brush
Maximisez vos compétences de réaction : créez une application de liste de tâches du début à la fin (avec TypeScript + Vite)par@emotta
10,346 lectures
10,346 lectures

Maximisez vos compétences de réaction : créez une application de liste de tâches du début à la fin (avec TypeScript + Vite)

par Eduardo Motta de Moraes26m2023/02/02
Read on Terminal Reader

Trop long; Pour lire

Dans cet article de blog, nous allons passer en revue tout ce dont vous avez besoin pour comprendre et créer une application React de base. Que vous soyez un débutant débutant avec React ou un développeur chevronné cherchant à parfaire vos compétences, ce guide est fait pour vous.
featured image - Maximisez vos compétences de réaction : créez une application de liste de tâches du début à la fin (avec TypeScript + Vite)
Eduardo Motta de Moraes HackerNoon profile picture
0-item

Dans cet article de blog, nous allons passer en revue tout ce dont vous avez besoin pour comprendre et créer une application React de base. Que vous soyez un débutant débutant avec React ou un développeur chevronné cherchant à perfectionner vos compétences, ce guide est fait pour vous.


Ce guide vous guidera tout au long du processus de création d'une application de liste de tâches entièrement fonctionnelle, y compris la conception, la mise en page, la gestion de l'état, etc. Nous utiliserons des composants fonctionnels et des crochets. Nous apprendrons comment utiliser l'état et les accessoires pour transmettre des données entre les composants, et comment gérer les entrées de l'utilisateur et mettre à jour l'état de votre application.


À la fin de ce guide, nous aurons une solide compréhension de la façon de créer une application React à partir de zéro, et vous pourrez utiliser vos nouvelles connaissances pour créer vos propres projets React.

Alors, commençons!


*Vous pouvez trouver le code de l'application que nous allons construire ici , et la version live ici .

Une brève introduction

Nous utiliserons TypeScript pour écrire le code et Vite pour développer et créer l'application.

Manuscrit

TypeScript est un langage de programmation fortement typé qui s'appuie sur JavaScript. Concrètement, si vous connaissez déjà JavaScript, il vous suffit d'apprendre à utiliser TypeScript, c'est à utiliser les types et les interfaces.


Les types et les interfaces nous permettent de définir les types de données que nous utilisons dans le code. Grâce à cela, nous pouvons détecter les bogues dès le début et éviter les problèmes sur toute la ligne.


Par exemple, si une fonction prend un number mais que nous lui passons une string , TypeScript se plaindra immédiatement :


 const someFunc = (parameter: number) => {...}; someFunc('1') // Argument of type 'string' is not assignable to parameter of type 'number'.


Si nous utilisions JavaScript, nous n'attraperions probablement le bogue que plus tard.


Nous n'avons pas toujours besoin de spécifier le type, car TypeScript peut les déduire automatiquement le plus souvent.


Vous pouvez apprendre les bases de TypeScript ici . (Ou ignorez simplement les types.)

Vite

Le moyen le plus courant de lancer une application React consiste probablement à utiliser create-react-app . Nous utiliserons Vite (prononcé comme "veet") à la place. Mais ne vous inquiétez pas, c'est tout aussi simple, mais plus efficace.


Avec des outils comme Webpack (utilisé par create-react-app sous le capot), toute votre application doit être regroupée dans un seul fichier avant de pouvoir être servie au navigateur. Vite, d'autre part, tire parti des modules ES natifs du navigateur pour rendre le regroupement plus efficace avec Rollup , en servant des parties du code source selon les besoins.


Vite peut également accélérer considérablement le temps de développement avec le remplacement de module à chaud - ce qui signifie que chaque fois que des modifications sont apportées au code source, seules les modifications sont mises à jour, plutôt que l'ensemble de l'application.


En plus de cela, Vite offre un support natif pour Typescript, JSX et TSX, CSS et plus encore.


De la même manière que create-react-app, Vite propose un outil appelé create-vite, qui nous permet de démarrer rapidement un nouveau projet en utilisant des modèles de base, y compris des options pour Vanilla JS, ou en utilisant des bibliothèques comme React.


Pour être clair, nous n'avons pas besoin d' un outil comme Vite ou create-react-app pour créer des applications React, mais ils nous facilitent la vie en prenant soin de configurer le projet, de regrouper le code, d'utiliser des transpilers et bien plus encore.

Plonger dans React

JSX / TSX

React nous permet d'ajouter du balisage directement dans le code qui sera ensuite compilé en JavaScript brut. Cela s'appelle JSX . Lorsque nous utilisons JSX, nous pouvons enregistrer nos fichiers au format .jsx pour JavaScript ou .tsx pour TypeScript.


Il ressemble à ceci :


 const element = <h1>Hello, world!</h1>;


Il est similaire au HTML, mais il est intégré dans le fichier JavaScript, et il nous permet de manipuler le balisage avec une logique de programmation. Nous pouvons également ajouter du code JavaScript à l'intérieur du JSX, tant qu'il se trouve entre accolades.


Par exemple, si nous avons un tableau de texte que nous voulons rendre sous forme d'éléments de paragraphe différents, nous pourrions faire ceci :


 const paragraphs = ["First", "Second", "Third"]; paragraphs.map((paragraph) => <p>{paragraph}</p>);


Et il serait compilé à quelque chose comme ceci:


 <p>First</p> <p>Second</p> <p>Third</p>


Mais si nous essayons de faire exactement cela, cela ne fonctionnera pas. C'est parce que React fonctionne avec des composants et que JSX doit être rendu à l'intérieur de ces composants.

Composants de réaction

Les composants React peuvent être écrits à l'aide de classes JavaScript ou simplement de fonctions simples. Nous nous concentrerons sur les composants fonctionnels, car ils constituent la méthode la plus récente et la plus recommandée pour écrire des composants React aujourd'hui.


Un composant est défini par une fonction qui retournera le JSX qui sera compilé et rendu par le navigateur. Donc, pour étendre l'exemple ci-dessus, si nous voulons rendre les éléments de paragraphe, cela ressemblerait à ceci :


 // Define the component const Component = () => { const paragraphs = ["First", "Second", "Third"]; return ( <> {paragraphs.map((paragraph) => ( <p>{paragraph}</p> ))} </> ); }; // Use the component in the same way you use an HTML element in the JSX const OtherComponent = () => { return <Component />; };

Accessoires

Maintenant, nous voulons peut-être réutiliser ce composant avec des informations différentes. Nous pouvons le faire en utilisant des accessoires - qui sont juste un objet JavaScript contenant des données.


Dans notre exemple, au lieu de coder en dur le tableau, nous pourrions le transmettre au composant. Le résultat sera le même, mais maintenant le composant sera réutilisable.


Si nous utilisons TypeScript, nous devons spécifier les types de données à l'intérieur de l'objet props (il n'y a pas de contexte pour ce qu'elles sont, donc TypeScript ne peut pas les déduire), qui dans ce cas est un tableau de chaînes ( string[] ).


 const Component = (props: { paragraphs: string[] }) => { <> {props.paragraphs.map((paragraph) => ( <p>{paragraph}</p> ))} </>; }; const OtherComponent = () => { const paragraphs = ["First", "Second", "Third"]; return <Component paragraphs={paragraphs} />; };

État

Si nous voulons créer un composant interactif, nous allons devoir stocker des informations dans l'état du composant, afin qu'il puisse "s'en souvenir".


Par exemple, si nous voulons définir un simple compteur indiquant le nombre de fois qu'un bouton est cliqué, nous avons besoin d'un moyen de stocker et de mettre à jour cette valeur. React nous permet de le faire avec le hook useState (un hook est une fonction qui vous permet de vous "accrocher" à l' état de React et aux fonctionnalités du cycle de vie ).


Nous appelons le crochet useState avec la valeur initiale, et il nous renvoie un tableau avec la valeur elle-même et une fonction pour la mettre à jour.


 import { useState } from "react"; const Counter = () => { const [count, setCount] = useState(0); return ( <> <span>{count}</span> <button onClick={() => setCount(count + 1)}>Increment count</button> </> ); };


Avec ces connaissances, nous sommes maintenant prêts à commencer à créer notre application React.

Création du projet

Dépendances

Pour utiliser Vite, nous aurons besoin d'un **nœud **et d'un gestionnaire de paquets.


Pour installer le nœud, choisissez simplement l'une des options ici en fonction de votre système et de vos configurations. Si vous utilisez Linux ou un Mac, vous pouvez également l'installer en utilisant Homebrew .


Le gestionnaire de paquets peut être npm ou yarn . Dans cet article, nous allons utiliser npm .

Création du projet

Il est ensuite temps de créer le projet. Dans le terminal, nous naviguons vers le répertoire où le projet sera créé, puis exécutons la commande create-vite.


 $ npm create vite@latest


Nous pouvons être invités à installer des packages supplémentaires (comme create-vite). Tapez y et appuyez sur Entrée pour continuer.


 Need to install the following packages: [email protected] Ok to proceed? (y)


Ensuite, nous serons invités à entrer les informations sur le projet.


Entrez le nom du projet. J'ai choisi my-react-project .


 ? Project name: › my-react-project


Sélectionnez React comme "framework".


React est techniquement une bibliothèque et non un framework , mais ne vous inquiétez pas.


 ? Select a framework: › - Use arrow-keys. Return to submit. Vanilla Vue ❯ React Preact Lit Svelte Others


Sélectionnez TypeScript + SWC comme variante.


SWC (acronyme de Speedy Web Compiler) est un compilateur TypeScript/JavaScript ultra-rapide écrit en Rust. Ils prétendent être "20x plus rapides que Babel sur un seul thread et 70x plus rapides sur quatre cœurs".


 ? Select a variant: › - Use arrow-keys. Return to submit. JavaScript TypeScript JavaScript + SWC ❯ TypeScript + SWC


C'est fait, le projet est créé. Pour le démarrer en mode développement, nous devons passer au répertoire du projet, installer les dépendances et exécuter la commande dev script.


 cd my-react-project npm install npm run dev


Après quelques secondes, nous verrons quelque chose de similaire à ceci :


 VITE v4.0.4 ready in 486 ms ➜ Local: http://localhost:5173/ ➜ Network: use --host to expose ➜ press h to show help


Si nous ouvrons notre navigateur et naviguons vers http://localhost:5173 /, nous verrons la page Vite + React par défaut :



Cela signifie que tout est comme il se doit et que nous pouvons commencer à travailler sur notre application.

Construire l'application

Structure des fichiers et configuration initiale

Si nous ouvrons le projet dans l'éditeur de code ou l'IDE de notre choix, nous devrions voir une structure de fichiers comme celle-ci :



Nous pouvons supprimer certains des fichiers réutilisables, car nous ne les utiliserons pas (tous les fichiers .svg et .css).

Le code dans la fonction App peut être supprimé pour nous laisser ceci :


 function App() { return ( ) } export default App


Nous reviendrons sur ce dossier plus tard.

Coiffant

Le style n'est pas l'objectif ici, mais nous utiliserons Tailwind CSS, qui est une bibliothèque qui nous permet de styliser les éléments HTML en leur ajoutant des classes. Suivez ces instructions pour voir les styles reflétés dans votre propre projet.


Sinon, vous pouvez simplement ignorer les classes dans le code.

Réflexion sur le design : disposition des composants

Le processus de conception fait partie intégrante du développement d'une application et ne doit pas être négligé.

Pour créer notre application de liste de tâches, nous devons d'abord penser à la disposition des composants.


Nous commençons par simuler une interface utilisateur de base et décrivant une hiérarchie des composants impliqués.


Si vous n'êtes pas un designer, il n'est pas nécessaire que ce soit parfait ou l'interface utilisateur finale en termes de couleurs et de placement exact - il est plus important de penser à la structure des composants.


Idéalement, nos composants ne devraient être responsables que d'une seule chose, selon le principe de la responsabilité unique .


Dans l'image ci-dessous, les noms en violet sont les composants que nous allons construire — tout le reste sont des éléments HTML natifs. S'ils sont l'un à l'intérieur de l'autre, cela signifie qu'il y aura probablement une relation parent-enfant.

Props : construire une version statique

Une fois que nous avons un croquis, nous pouvons commencer à créer une version statique de l'application. C'est-à-dire, juste les éléments de l'interface utilisateur, mais sans interactivité pour le moment. Cette partie est assez simple et implique beaucoup de frappe et peu de réflexion une fois que vous avez compris.


Vous pouvez trouver le code de la version statique dans ce dépôt GitHub , dans la branche « static-version ». Le code de l'application entièrement fonctionnelle est la branche principale.


Récipient


Comme indiqué ci-dessus, nous allons avoir un conteneur qui sera réutilisé pour chaque section de l'application. Ce Container montre une des manières de composer différents éléments : en les passant comme enfants.


 // src/components/Container.tsx const Container = ({ children, title, }: { children: JSX.Element | JSX.Element[]; title?: string; }) => { return ( <div className="bg-green-600 p-4 border shadow rounded-md"> {title && <h2 className="text-xl pb-2 text-white">{title}</h2>} <div>{children}</div> </div> ); }; export default Container;


Il prend un objet props avec un paramètre children de type JSX.Element | JSX.Element[] . Cela signifie que nous pouvons le composer avec n'importe quel autre élément HTML ou tout autre composant que nous créons. Il peut être rendu où nous voulons à l'intérieur du conteneur - dans ce cas à l'intérieur de la deuxième div.


Dans notre application, il va afficher chaque section (définie ci-dessous) lorsque nous les utilisons dans le composant App.


Le conteneur prend également un accessoire string facultatif nommé title , qui sera rendu à l'intérieur d'un h2 chaque fois qu'il existe.


 // src/App.tsx import Container from "./components/Container"; import Input from "./components/Input"; import Summary from "./components/Summary/Summary"; import Tasks from "./components/Tasks/Tasks"; function App() { return ( <div className="flex justify-center m-5"> <div className="flex flex-col items-center"> <div className="sm:w-[640px] border shadow p-10 flex flex-col gap-10"> <Container title={"Summary"}> <Summary /> </Container> <Container> <Input /> </Container> <Container title={"Tasks"}> <Tasks /> </Container> </div> </div> </div> ); } export default App;


Résumé


La première section est un résumé (composant Résumé) affichant trois éléments (SummaryItem) : le nombre total de tâches, le nombre de tâches en attente et le nombre de tâches terminées. C'est une autre façon de composer des composants : il suffit de les utiliser dans l'instruction return d'un autre composant.


(Cependant, il est important de ne jamais définir un composant à l'intérieur d'un autre composant, car cela peut entraîner des rendus inutiles et des bogues.)


Pour l'instant, nous pouvons simplement utiliser des données statiques dans les deux composants.


 // src/components/Summary/SummaryItem.tsx const SummaryItem = ({ itemName, itemValue, }: { itemName: string; itemValue: number; }) => { return ( <article className="bg-green-50 w-36 rounded-sm flex justify-between p-2"> <h3 className="font-bold">{itemName}</h3> <span className="bg-green-900 text-white px-2 rounded-sm"> {itemValue} </span> </article> ); }; export default SummaryItem; // src/components/Summary/Summary.tsx import SummaryItem from "./SummaryItem"; const Summary = () => { return ( <> <div className="flex justify-between"> <SummaryItem itemName={"Total"} itemValue={3} /> <SummaryItem itemName={"To do"} itemValue={2} /> <SummaryItem itemName={"Done"} itemValue={1} /> </div> </> ); }; export default Summary;


Vous remarquerez que SummaryItem prend deux props : itemName , de type string, et itemValue , de type number . Ces accessoires sont transmis lorsque le composant SummaryItem est utilisé dans le composant Summary, puis rendus dans SummaryItem JSX.


Tâches


De même, pour la section des tâches (la dernière), nous avons un composant Tasks qui restitue les composants TaskItem.


Aussi avec des données statiques pour l'instant. Nous devrons ensuite transmettre un nom de tâche et un statut en tant qu'accessoires au composant TaskItem pour le rendre réutilisable et dynamique.


 // src/components/Tasks/TaskItem.tsx const TaskItem = () => { return ( <div className="flex justify-between bg-white p-1 px-3 rounded-sm"> <div className="flex gap-2 items-center"> <input type="checkbox" /> Task name </div> <button className="bg-green-200 hover:bg-green-300 rounded-lg p-1 px-3"> Delete </button> </div> ); }; export default TaskItem; // src/components/Tasks/Tasks.tsx import TaskItem from "./TaskItem"; const Tasks = () => { return ( <div className="flex flex-col gap-2"> <TaskItem /> </div> ); }; export default Tasks;


Saisir


Enfin, le composant Input est un formulaire avec une étiquette, une entrée de type texte et un bouton pour "Ajouter une tâche". Pour l'instant, cela ne fait rien, mais nous allons bientôt changer cela.


 // src/components/Input.tsx const InputContainer = () => { return ( <form action="" className="flex flex-col gap-4"> <div className="flex flex-col"> <label className="text-white">Enter your next task:</label> <input className="p-1 rounded-sm" /> </div> <button type="button" className="bg-green-100 rounded-lg hover:bg-green-200 p-1" > Add task </button> </form> ); }; export default InputContainer;

État : ajouter de l'interactivité

Pour ajouter de l'interactivité dans React, nous devons stocker des informations dans l'état du composant.


Mais avant de faire cela, nous devons réfléchir à la façon dont nous voulons que les données changent au fil du temps. Nous devons identifier une représentation minimale de ces données et identifier les composants que nous devons utiliser pour stocker cet état.


Une représentation minimale de l'État


L'état doit contenir toutes les informations nécessaires pour rendre notre application interactive, mais rien de plus. Si nous pouvons calculer une valeur à partir d'une valeur différente, nous devons en garder une seule dans l'état. Cela rend notre code non seulement moins verbeux, mais aussi moins sujet aux bogues impliquant des valeurs d'état contradictoires.


Dans notre exemple, nous pourrions penser que nous devons suivre les valeurs des tâches totales, des tâches en attente et des tâches terminées.


Mais pour suivre les tâches, il suffit d'avoir un seul tableau avec des objets représentant chaque tâche et son statut (en attente ou terminé).


 const tasks = [ { name: "task one", done: false, }, { name: "task two", done: true, }, ];


Avec ces données, nous pouvons toujours trouver toutes les autres informations dont nous avons besoin au moment du rendu en utilisant des méthodes de tableau. Nous évitons également la possibilité de contradictions, comme avoir un total de 4 tâches, mais seulement 1 tâche en attente et 1 tâche terminée, par exemple.


Nous avons également besoin d'un état dans notre formulaire (dans le composant Input) afin de pouvoir le rendre interactif.


Où l'État devrait vivre


Pensez-y de cette façon : quels composants doivent accéder aux données que nous allons stocker dans l'état ? S'il s'agit d'un seul composant, l'état peut vivre dans ce composant lui-même. Si plusieurs composants ont besoin des données, vous devez trouver le parent commun à ces composants.


Dans notre exemple, l'état nécessaire pour contrôler le composant Input n'a besoin d'être accessible qu'à cet endroit, il peut donc être local à ce composant.


 // src/components/Input.tsx import { useState } from "react"; const InputContainer = () => { const [newTask, setNewTask] = useState(""); // Initialize newTask and setNewTask return ( <form action="" className="flex flex-col gap-4"> <div className="flex flex-col"> <label className="text-white">Enter your next task:</label> <input className="p-1 rounded-sm" type="text" value={newTask} // Set the input value to newTask onChange={(e) => setNewTask(e.target.value)} // Set newTask to the input value whenever the user types something /> </div> <button type="submit" className="bg-green-100 rounded-lg hover:bg-green-200 p-1" > Add task </button> </form> ); }; export default InputContainer;


Ce que cela fait, c'est afficher notre valeur newTask dans l'entrée et appeler la fonction setNewTask chaque fois que l'entrée change (c'est-à-dire lorsque l'utilisateur tape quelque chose).


Nous ne verrons aucun changement immédiat dans l'interface utilisateur, mais cela est nécessaire pour que nous puissions contrôler l'entrée et avoir accès à sa valeur pour l'utiliser plus tard.


L'état pour suivre les tâches, cependant, doit être géré différemment, car il doit être accessible dans les composants SummaryItem (nous devons afficher le nombre de tâches totales, en attente et terminées) ainsi que dans les composants TaskItem (nous avons besoin pour afficher les tâches elles-mêmes). Et il doit être dans le même état car ces informations doivent toujours être synchronisées.


Jetons un coup d'œil à notre arborescence de composants (vous pouvez utiliser les outils de développement React pour cela).



Nous pouvons voir que le premier composant parent commun est App. C'est donc là que notre état pour les tâches va vivre.


Avec l'état en place, il ne restera plus qu'à transmettre les données en tant qu'accessoires aux composants qui doivent les utiliser.


(Nous ne nous inquiétons pas encore de la manière d'apporter et de conserver les modifications apportées à l'état parent, c'est la partie suivante.)


 // src/App.tsx import { useState } from "react"; import { v4 as uuidv4 } from "uuid"; import Container from "./components/Container"; import Input from "./components/Input"; import Summary from "./components/Summary/Summary"; import Tasks from "./components/Tasks/Tasks"; export interface Task { name: string; done: boolean; id: string; } const initialTasks = [ { name: "task one", done: false, id: uuidv4(), }, { name: "task two", done: true, id: uuidv4(), }, ]; function App() { const [tasks, setTasks] = useState<Task[]>(initialTasks); return ( <div className="flex justify-center m-5"> <div className="flex flex-col items-center"> <div className="border shadow p-10 flex flex-col gap-10 sm:w-[640px]"> <Container title={"Summary"}> <Summary tasks={tasks} /> </Container> <Container> <Input /> </Container> <Container title={"Tasks"}> <Tasks tasks={tasks} /> </Container> </div> </div> </div> ); } export default App;


Ici, nous initialisons la valeur des tâches avec des données factices ( initialTasks ), juste pour pouvoir la visualiser avant la fin de l'application. Plus tard, nous pouvons le changer en un tableau vide, de sorte qu'un nouvel utilisateur ne verra aucune tâche lors de l'ouverture de l'application.


Outre le name et les propriétés done , nous ajoutons également un identifiant à nos objets de tâche, car cela sera bientôt nécessaire.


Nous définissons une interface avec les types de la valeur dans les objets de tâche et la transmettons à la fonction useState . Ceci est nécessaire dans ce cas, car TypeScript ne pourra pas le déduire lorsque nous changeons la valeur initiale des tasks en un tableau vide, ou lorsque nous le passons comme accessoires.


Enfin, notez que nous transmettons les tâches en tant qu'accessoires aux composants Résumé et Tâches. Ces composants devront être changés pour s'adapter à cela.


 // src/components/Summary/Summary.tsx import { Task } from "../../App"; import SummaryItem from "./SummaryItem"; const Summary = ({ tasks }: { tasks: Task[] }) => { const total = tasks.length; const pending = tasks.filter((t) => t.done === false).length; const done = tasks.filter((t) => t.done === true).length; return ( <> <div className="flex flex-col gap-1 sm:flex-row sm:justify-between"> <SummaryItem itemName={"Total"} itemValue={total} /> <SummaryItem itemName={"To do"} itemValue={pending} /> <SummaryItem itemName={"Done"} itemValue={done} /> </div> </> ); }; export default Summary;


Nous avons mis à jour le composant Summary afin qu'il accepte désormais les tasks en tant que prop. Nous avons également défini les valeurs total , pending et done , qui seront transmises en tant qu'accessoires aux composants SummaryItem à la place des itemValue statiques que nous avions auparavant.


 // src/components/Tasks/Tasks.tsx import { Task } from "../../App"; import TaskItem from "./TaskItem"; const Tasks = ({ tasks }: { tasks: Task[] }) => { return ( <div className="flex flex-col gap-2"> {tasks.map((t) => ( <TaskItem key={t.id} name={t.name} /> ))} </div> ); }; export default Tasks; // src/components/Tasks/TaskItem.tsx import { useState } from "react"; const TaskItem = ({ name }: { name: string }) => { const [done, setDone] = useState(false); return ( <div className="flex justify-between bg-white p-1 px-3 rounded-sm gap-4"> <div className="flex gap-2 items-center"> <input type="checkbox" checked={done} onChange={() => setDone(!done)} /> {name} </div> <button className="bg-green-200 hover:bg-green-300 rounded-lg p-1 px-3"> Delete </button> </div> ); }; export default TaskItem;


Pour le composant Tasks, nous prenons également la task comme accessoire et mappons sa propriété name aux composants TaskItem. En conséquence, nous obtenons un composant TaskItem pour chaque objet à l'intérieur du tableau des tasks . Nous mettons également à jour le composant TaskItem pour accepter le name comme accessoire.


C'est là que l'identifiant est utile, car nous devons transmettre une clé unique à chaque fois que nous avons une liste de composants enfants. Si nous n'ajoutons pas la clé, cela pourrait entraîner des bogues sur rerender . (Dans une application de production, l'identifiant proviendrait très probablement du backend.)


Le résultat pour l'instant est celui-ci :



Nous pouvons déjà voir les numéros récapitulatifs et les noms des tâches reflétant nos données factices. Mais nous manquons toujours d'un moyen d'ajouter ou de supprimer des tâches.


Ajout d'un flux de données inverse


Pour terminer notre application, nous avons besoin d'un moyen de modifier l'état du composant App (où se trouvent les données de tâches) à partir des composants enfants Input et TaskItem.


Pour ce faire, nous pouvons utiliser les fonctions générées par le crochet useState pour définir les gestionnaires d'événements et les transmettre en tant qu'accessoires. Une fois que nous avons fait cela, nous les appelons simplement lors de l'interaction utilisateur appropriée à partir des composants enfants.


Assurez-vous de ne jamais muter l'état chaque fois que vous le mettez à jour , car cela conduirait à des bogues. Remplacez toujours l'objet d'état par un nouveau lors de sa mise à jour.


Vous trouverez ci-dessous notre dernier composant App avec les gestionnaires déclarés et transmis en tant qu'accessoires aux composants Input et Tasks.


handleSubmit renvoie un nouveau tableau avec les anciennes tâches plus la nouvelle. toggleDoneTask renvoie un nouveau tableau avec la propriété done opposée, pour l' id spécifié. handleDeleteTask renvoie un nouveau tableau sans la tâche avec l' id spécifié.


 // src/App.tsx import { FormEvent, useState } from "react"; import { v4 as uuidv4 } from "uuid"; import Container from "./components/Container"; import Input from "./components/Input"; import Summary from "./components/Summary/Summary"; import Tasks from "./components/Tasks/Tasks"; export interface Task { name: string; done: boolean; id: string; } function App() { const [tasks, setTasks] = useState<Task[]>([]); const handleSubmit = (e: FormEvent<HTMLFormElement>, value: string) => { e.preventDefault(); const newTask = { name: value, done: false, id: uuidv4(), }; setTasks((tasks) => [...tasks, newTask]); }; const toggleDoneTask = (id: string, done: boolean) => { setTasks((tasks) => tasks.map((t) => { if (t.id === id) { t.done = done; } return t; }) ); }; const handleDeleteTask = (id: string) => { setTasks((tasks) => tasks.filter((t) => t.id !== id)); }; return ( <div className="flex justify-center m-5"> <div className="flex flex-col items-center"> <div className="border shadow p-10 flex flex-col gap-10 sm:w-[640px]"> <Container title={"Summary"}> <Summary tasks={tasks} /> </Container> <Container> <Input handleSubmit={handleSubmit} /> </Container> <Container title={"Tasks"}> <Tasks tasks={tasks} toggleDone={toggleDoneTask} handleDelete={handleDeleteTask} /> </Container> </div> </div> </div> ); } export default App;


Il s'agit du dernier composant Input utilisant handleSubmit pour mettre à jour l'état du composant App.


 // src/components/Input.tsx import { FormEvent, useState } from "react"; const InputContainer = ({ handleSubmit, }: { handleSubmit: (e: FormEvent<HTMLFormElement>, value: string) => void; }) => { const [newTaskName, setNewTaskName] = useState(""); return ( <form action="" className="flex flex-col gap-4" onSubmit={(e) => { handleSubmit(e, newTaskName); setNewTaskName(""); }} > <div className="flex flex-col"> <label className="text-white">Enter your next task:</label> <input className="p-1 rounded-sm" type="text" value={newTaskName} onChange={(e) => setNewTaskName(e.target.value)} /> </div> <button type="submit" className="bg-green-100 rounded-lg hover:bg-green-200 p-1" > Add task </button> </form> ); }; export default InputContainer;


Il s'agit du dernier composant Tasks, que nous avons mis à jour pour transmettre les accessoires de App à TaskItem. Nous avons également ajouté un opérateur ternaire pour renvoyer "Aucune tâche pour le moment !" lorsqu'il n'y a pas de tâches.


 // src/components/Tasks/Tasks.tsx import { Task } from "../../App"; import TaskItem from "./TaskItem"; const Tasks = ({ tasks, toggleDone, handleDelete, }: { tasks: Task[]; toggleDone: (id: string, done: boolean) => void; handleDelete: (id: string) => void; }) => { return ( <div className="flex flex-col gap-2"> {tasks.length ? ( tasks.map((t) => ( <TaskItem key={t.id} name={t.name} done={t.done} id={t.id} toggleDone={toggleDone} handleDelete={handleDelete} /> )) ) : ( <span className="text-green-100">No tasks yet!</span> )} </div> ); }; export default Tasks;


Et c'est le dernier composant TaskItem, utilisant toggleDone et handleDelete pour mettre à jour l'état du composant App.


 // src/components/Tasks/TaskItem.tsx const TaskItem = ({ name, done, id, toggleDone, handleDelete, }: { name: string; done: boolean; id: string; toggleDone: (id: string, done: boolean) => void; handleDelete: (id: string) => void; }) => { return ( <div className="flex justify-between bg-white p-1 px-3 rounded-sm gap-4"> <div className="flex gap-2 items-center"> <input type="checkbox" checked={done} onChange={() => toggleDone(id, !done)} /> {name} </div> <button className="bg-green-200 hover:bg-green-300 rounded-lg p-1 px-3" type="button" onClick={() => handleDelete(id)} > Delete </button> </div> ); }; export default TaskItem;


Et voici notre application finale après avoir ajouté quelques tâches !



Si vous êtes en train de coder, vous pouvez déployer votre propre application en suivant ces instructions .

Vous pouvez trouver le référentiel avec tout le code que nous avons parcouru ici , et la version live de l'application ici .

Derniers mots

En conclusion, la création d'une application de liste de tâches peut être un excellent moyen d'apprendre et de consolider notre compréhension de React et de ses principes. En décomposant le processus en petites étapes et en suivant les meilleures pratiques, nous pouvons créer une application fonctionnelle en un temps relativement court.


Nous avons couvert :


  • les concepts clés de composants, d'état et de flux de données inverse.

  • la conception et l'architecture de l'application.

  • les meilleures pratiques telles que le principe de responsabilité unique


En suivant les étapes décrites dans ce guide, vous devriez maintenant avoir une solide compréhension de la façon de créer une application React simple et être en mesure de l'appliquer à vos propres projets.


Bon codage !