Pourquoi React avec TypeScript : guide complet ?
Contexte réel : pourquoi un dev a besoin de ca au quotidien
Dans l'industrie moderne, le développement web évolue rapidement. Les applications modernes sont complexes et nécessitent une gestion robuste des états et des données. L'utilisation de TypeScript avec React permet une meilleure type-sécurité et une meilleure maintenance du code. En effet, TypeScript ajoute un niveau d'abstraction supplémentaire qui aident les développeurs à prévenir les erreurs de typage au moment de la compilation plutôt que de l'exécution.
Un cas d'utilisation concret en 2-3 phrases
Imaginons une application e-commerce. Elle nécessite un système robuste pour gérer le stock des produits, les commandes et les utilisateurs. En utilisant React avec TypeScript, nous pouvons assurer que chaque composant reçoit les bons types de props et que toutes les données sont traitées de manière sécurisée et cohérente.
Prerequis
Connaissances nécessaires
- JavaScript ES6+
- Concepts de base de React (components, state, props)
- Typescript (principes de base)
Outils à installer
- Node.js : Version >= 14.x
- npm ou yarn
- TypeScript :
npm install -g typescript
Concepts fondamentaux
1. Composants React avec TypeScript
Les composants React sont des fonctions ou des classes qui renvoient un JSX.
// Exemple d'un composant fonctionnel avec TypeScript
import React from 'react';
interface Props {
title: string;
}
const Header: React.FC<Props> = ({ title }) => {
return <h1>{title}</h1>;
};
export default Header;
2. State et Props
Les props et les state sont des objets qui contiennent des données.
// Exemple d'un composant avec état initialisé avec TypeScript
import React, { useState } from 'react';
interface CounterProps {
initialValue: number;
}
const Counter: React.FC<CounterProps> = ({ initialValue }) => {
const [count, setCount] = useState<number>(initialValue);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
};
export default Counter;
3. Types de données
TypeScript permet de définir des types spécifiques pour les props et l'état.
// Exemple d'utilisation de types personnalisés
interface User {
id: number;
name: string;
email: string;
}
const user: User = {
id: 1,
name: 'John Doe',
email: 'john.doe@example.com'
};
4. Types d'événements
Les événements en React peuvent être typés avec TypeScript.
// Exemple de gestion des événements avec TypeScript
import React from 'react';
interface InputProps {
onInputChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
}
const Input: React.FC<InputProps> = ({ onInputChange }) => {
return <input type="text" onChange={onInputChange} />;
};
export default Input;
Mise en pratique : projet fil rouge
Construisons un gestionnaire de tâches avec React et TypeScript
Nous allons créer un simple application pour gérer les tâches à faire. L'application aura une liste des tâches, le pouvoir d'ajouter et de supprimer des tâches.
Étape 1 : Initialiser le projet
npx create-react-app task-manager-ts --template typescript
cd task-manager-ts
Étape 2 : Créer les composants
Créez un fichier Task.tsx pour le composant de tâche.
// Task.tsx
import React from 'react';
interface TaskProps {
id: number;
text: string;
onDelete: (id: number) => void;
}
const Task: React.FC<TaskProps> = ({ id, text, onDelete }) => {
return (
<li>
{text}
<button onClick={() => onDelete(id)}>Delete</button>
</li>
);
};
export default Task;
Créez un fichier TaskForm.tsx pour le formulaire d'ajout de tâche.
// TaskForm.tsx
import React, { useState } from 'react';
interface TaskFormProps {
onAdd: (text: string) => void;
}
const TaskForm: React.FC<TaskFormProps> = ({ onAdd }) => {
const [text, setText] = useState('');
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
if (text.trim()) {
onAdd(text);
setText('');
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Add a new task"
/>
<button type="submit">Add</button>
</form>
);
};
export default TaskForm;
Étape 3 : Créer le composant principal
Créez un fichier App.tsx pour le composant principal.
// App.tsx
import React, { useState } from 'react';
import Task from './Task';
import TaskForm from './TaskForm';
interface Task {
id: number;
text: string;
}
const App: React.FC = () => {
const [tasks, setTasks] = useState<Task[]>([]);
const addTask = (text: string) => {
const newTask: Task = { id: Date.now(), text };
setTasks([...tasks, newTask]);
};
const deleteTask = (id: number) => {
setTasks(tasks.filter(task => task.id !== id));
};
return (
<div>
<h1>Task Manager</h1>
<TaskForm onAdd={addTask} />
<ul>
{tasks.map(task => (
<Task key={task.id} {...task} onDelete={deleteTask} />
))}
</ul>
</div>
);
};
export default App;
Étape 4 : Exécuter le projet
npm start
Erreurs fréquentes et debugging
1. Mauvais type de props
// ❌ Mauvais
const Header: React.FC = ({ title }) => {
return <h1>{title}</h1>;
};
// ✅ Correct
interface Props {
title: string;
}
const Header: React.FC<Props> = ({ title }) => {
return <h1>{title}</h1>;
};
2. Mauvaise utilisation de useState
// ❌ Mauvais
const [count, setCount] = useState();
// ✅ Correct
const [count, setCount] = useState<number>(0);
3. Prop non défini
// ❌ Mauvais
const Task: React.FC<TaskProps> = ({ id, text, onDelete }) => {
return (
<li>
{text}
<button onClick={onDelete}>Delete</button>
</li>
);
};
// ✅ Correct
interface TaskProps {
id: number;
text: string;
onDelete?: () => void;
}
const Task: React.FC<TaskProps> = ({ id, text, onDelete }) => {
return (
<li>
{text}
{onDelete && <button onClick={onDelete}>Delete</button>}
</li>
);
};
Pour aller plus loin
1. TypeScript pour les hooks personnalisés
Les hooks personnalisés permettent de partager la logique entre des composants.
// useTask.ts
import { useState } from 'react';
interface Task {
id: number;
text: string;
}
const useTask = () => {
const [tasks, setTasks] = useState<Task[]>([]);
const addTask = (text: string) => {
const newTask: Task = { id: Date.now(), text };
setTasks([...tasks, newTask]);
};
const deleteTask = (id: number) => {
setTasks(tasks.filter(task => task.id !== id));
};
return { tasks, addTask, deleteTask };
};
export default useTask;
2. TypeScript avec les contextes React
Les contextes permettent de partager des données entre les composants sans avoir à passer les props manuellement.
// TaskContext.tsx
import React, { createContext, useContext } from 'react';
import useTask from './useTask';
interface TaskContextType {
tasks: Task[];
addTask: (text: string) => void;
deleteTask: (id: number) => void;
}
const TaskContext = createContext<TaskContextType>({
tasks: [],
addTask: () => {},
deleteTask: () => {}
});
export const useTaskContext = () => useContext(TaskContext);
interface TaskProviderProps {
children: React.ReactNode;
}
export const TaskProvider: React.FC<TaskProviderProps> = ({ children }) => {
const taskContextValue = useTask();
return (
<TaskContext.Provider value={taskContextValue}>
{children}
</TaskContext.Provider>
);
};
3. TypeScript avec les tests unitaires
Les tests unitaires permettent de vérifier que chaque composant fonctionne correctement.
// Task.test.tsx
import React from 'react';
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import Task from './Task';
test('renders task', () => {
const id = 1;
const text = 'Learn TypeScript';
render(<Task id={id} text={text} onDelete={() => {}} />);
expect(screen.getByText(text)).toBeInTheDocument();
});
Défi pratique
Créez un composant TodoList qui utilise les hooks personnalisés et les contextes React pour gérer une liste de tâches. Le composant devrait avoir des fonctionnalités d'ajout, de suppression et de mise à jour des tâches.