Présentation des discriminated union types en Typescript

Publié le 12 mars 2024
Dimitri Dumont avatar
Dimitri Dumont
Développeur front-end

Les discriminated union types en Typescript offrent une puissante abstraction pour gérer différentes variantes de données de manière sûre et efficace.

Dans cet article, nous allons explorer en détail ce que sont les discriminated union types et comment les utiliser dans un environnement React & Next.js, en fournissant des exemples concrets.

Qu'est-ce qu'un discriminated union type ?

Un discriminated union type en Typescript est une technique qui permet de définir un type qui peut représenter plusieurs variantes de valeurs, mais avec un moyen clair de distinguer entre ces variantes.

Cela se fait en utilisant une propriété commune à toutes les variantes, souvent appelée discriminant. Cela offre un moyen élégant de manipuler des données polymorphiques tout en profitant du système de typage statique de Typescript.

1type Circle = {
2 kind: 'circle';
3 radius: number;
4};
5
6type Square = {
7 kind: 'square';
8 sideLength: number;
9};
10
11type Triangle = {
12 kind: 'triangle';
13 base: number;
14 height: number;
15};
16
17type Shape = Circle | Square | Triangle;
18
19// Fonction pour calculer l'aire d'une forme
20function calculateArea(shape: Shape): number {
21 switch (shape.kind) {
22 case 'circle':
23 return Math.PI * shape.radius ** 2;
24 case 'square':
25 return shape.sideLength ** 2;
26 case 'triangle':
27 return (shape.base * shape.height) / 2;
28 default:
29 throw new Error('Forme non prise en charge');
30 }
31}
32
33// Exemple d'utilisation
34const circle: Circle = { kind: 'circle', radius: 5 };
35const square: Square = { kind: 'square', sideLength: 4 };
36const triangle: Triangle = { kind: 'triangle', base: 3, height: 6 };
37
38console.log('Aire du cercle:', calculateArea(circle)); // Affiche: Aire du cercle: 78.53981633974483
39console.log('Aire du carré:', calculateArea(square)); // Affiche: Aire du carré: 16
40console.log('Aire du triangle:', calculateArea(triangle)); // Affiche: Aire du triangle: 9

Dans cet exemple, Shape est un discriminated union type qui peut représenter trois types différents de formes géométriques : cercle, carré et triangle. Chaque variante de Shape est distinguée par la propriété kind, qui agit comme un discriminant. Les types Circle, Square et Triangle définissent les propriétés spécifiques de chaque forme, telles que radius pour un cercle, sideLength pour un carré et ainsi de suite.

La fonction calculateArea prend une forme (Shape) en paramètre et utilise un switch sur la propriété kind pour déterminer le type spécifique de la forme et calculer son aire en conséquence.

Cet exemple démontre comment un discriminated union type peut être utilisé pour manipuler différentes variantes de données de manière sûre et précise, en permettant à Typescript de déterminer le type spécifique de chaque instance en fonction de la valeur de son discriminant.

Dans les prochains exemples, nous allons voir comment utiliser les discriminated union types dans un environnement React & Next.js.

hexa web logo
Hexa web
Des conseils pour un projet web ?
Nous contacter

Appel API versionné

L'un des premiers cas d'usage des discriminated union types en front-end est la gestion des appels API versionnés. Il peut arriver que vous ayez une API qui renvoie des réponses différentes en fonction de la version demandée. Vous pouvez utiliser un discriminated union type pour modéliser ces différentes réponses et les traiter de manière appropriée dans votre application Next.js.

Pour cet exemple, nous souhaitons afficher les informations de l'utilisateur récupérées à partir de l'API dans notre application. Nous pouvons utiliser un discriminant union type pour gérer les différentes versions de données de l'utilisateur et les afficher en conséquence.

Voici un exemple de composant React utilisant le discriminant union type pour afficher les informations de l'utilisateur :

src/api.ts
1export type APIVersion = 'v1' | 'v2';
2
3type UserV1 = {
4 version: 'v1';
5 data: {
6 name: string;
7 email: string;
8 };
9};
10
11type UserV2 = {
12 version: 'v2';
13 data: {
14 fullName: string;
15 email: string;
16 };
17};
18
19export type User = UserV1 | UserV2;
20
21async function getUser(version: APIVersion): Promise<User> {
22 const response = await fetch(`https://api.example.com/${version}/data`);
23 return await response.json();
24}
src/components/user-info.tsx
1import React, { useEffect, useState } from 'react';
2import { getUser, APIVersion, User } from '@/api';
3
4const UserInfo: React.FC<{ version: APIVersion }> = ({ version }) => {
5 const [userData, setUserData] = useState<User | null>(null);
6
7 useEffect(() => {
8 const fetchData = async () => {
9 try {
10 const { data } = await getUser(version);
11 setUserData(data);
12 } catch (error) {
13 console.error('Erreur lors de la récupération des données :', error);
14 }
15 };
16
17 fetchData();
18 }, [version]);
19
20 if (!userData) {
21 return <div>Chargement...</div>;
22 }
23
24 return (
25 <div>
26 <h2>Informations Utilisateur</h2>
27 {userData.version === 'v1' && (
28 <div>
29 <p>Nom: {userData.name}</p>
30 <p>Email: {userData.email}</p>
31 </div>
32 )}
33 {userData.version === 'v2' && (
34 <div>
35 <p>Nom complet: {userData.fullName}</p>
36 <p>Email: {userData.email}</p>
37 </div>
38 )}
39 </div>
40 );
41};
42
43export default UserInfo;
44

Dans cet exemple, le composant UserInfo appelle l'API en utilisant la fonction getUser avec la version spécifiée. Une fois les données récupérées, il affiche les informations de l'utilisateur en fonction de la version de données reçue.

Il utilise le discriminant version du state userData pour déterminer quelle version de données de l'utilisateur est reçue et affiche les informations appropriées en conséquence.

Ainsi, grâce à un discriminaeted union type, vous pouvez gérer différentes versions de votre API de manière sûre et précise dans votre application Next.js.

Gestion des états dans un composant React

Un autre cas d'usage des discriminated union type en front-end est la gestion des états d'un composant React tels que "chargement", "erreur" ou "succès".

1type LoadingState = { status: 'loading' };
2type SuccessState<T> = { status: 'success'; data: T };
3type ErrorState = { status: 'error'; message: string };
4
5type State<T> = LoadingState | SuccessState<T> | ErrorState;
6
7function MyComponent<T>({ state }: { state: State<T> }) {
8 switch (state.status) {
9 case 'loading':
10 return <div>Chargement...</div>;
11 case 'success':
12 return <div>Données chargées: {state.data}</div>;
13 case 'error':
14 return <div>Erreur: {state.message}</div>;
15 default:
16 return null;
17 }
18}
19

Dans cet exemple, le composant MyComponent utilise un discriminated union type pour modéliser les différents états possibles, tels que loading, success et error, et affiche le contenu du composant en fonction de l'état. Ici, le discriminant est le champ status.

Gestion des actions dans une application Next.js

Une librairie populaire qui utilise les discriminated union types est Redux avec ses types d'actions.

1type AddTodoAction = {
2 type: 'ADD_TODO';
3 payload: string;
4};
5
6type ToggleTodoAction = {
7 type: 'TOGGLE_TODO';
8 payload: number;
9};
10
11type ClearTodosAction = {
12 type: 'CLEAR_TODOS';
13};
14
15type TodoAction = AddTodoAction | ToggleTodoAction | ClearTodosAction;
16
17export const addTodo = (text: string): AddTodoAction => ({
18 type: 'ADD_TODO',
19 payload: text,
20});
21
22export const toggleTodo = (id: number): ToggleTodoAction => ({
23 type: 'TOGGLE_TODO',
24 payload: id,
25});
26
27export const clearTodos = (): ClearTodosAction => ({
28 type: 'CLEAR_TODOS',
29});
30
1
2type TodoState = {
3 todos: { id: number; text: string; completed: boolean }[];
4}
5
6const initialState: TodoState = {
7 todos: [],
8};
9
10const todoReducer = (state = initialState, action: TodoAction): TodoState => {
11 switch (action.type) {
12 case 'ADD_TODO':
13 return {
14 ...state,
15 todos: [...state.todos, { id: state.todos.length + 1, text: action.payload, completed: false }],
16 };
17 case 'TOGGLE_TODO':
18 return {
19 ...state,
20 todos: state.todos.map(todo =>
21 todo.id === action.payload ? { ...todo, completed: !todo.completed } : todo
22 ),
23 };
24 case 'CLEAR_TODOS':
25 return {
26 ...state,
27 todos: [],
28 };
29 default:
30 return state;
31 }
32};
33

Dans cet exemple, nous utilisons un discriminated union type pour définir les différents types d'actions TodoAction. Chaque action a un type spécifique type et éventuellement des données payload. Nous utilisons également des action creators pour créer des actions.

Dans le reducer, nous utilisons le discriminated union type pour filtrer les différentes actions et exécuter la logique appropriée en fonction du type d'action.

En utilisant un discriminated union type, nous pouvons garantir que toutes les actions sont correctement typées et que Redux peut inférer automatiquement le type de l'action et du state dans les reducers et les action creators. Cela rend le code plus sûr et plus facile à maintenir.

hexa web logo
Hexa web
Des conseils pour un projet web ?
Nous contacter

Gestion des variants de contenu dans les composants React

Il peut arriver que nous devions gérer des variants d'un contenu dans nos composants React, comme par exemple des médias.

1type Image = { type: 'image'; url: string; alt: string };
2type Video = { type: 'video'; url: string; caption: string };
3
4type Media = Image | Video;
5
6function renderMedia(media: Media) {
7 switch (media.type) {
8 case 'image':
9 return <img src={media.url} alt={media.alt} />;
10 case 'video':
11 return (
12 <div>
13 <video src={media.url} controls />
14 <p>{media.caption}</p>
15 </div>
16 );
17 default:
18 return null;
19 }
20}
21

Comme pour les autres exemples, nous utilisons un discriminant représenté ici par le champ type pour afficher correctement les différents types de média.

Conclusion

Les discriminated union types sont une fonctionnalité puissante de Typescript qui permettent de modéliser des types de données complexes et variés de manière concise et sûre. Que ce soit pour modéliser différentes versions de réponses API, gérer les états des composants React, définir des actions Redux ou représenter des variantes de contenu, les discriminated union types offrent une solution qui rend le code plus robuste, plus lisible et évite des erreurs.

Grâce à leur capacité à distinguer les différentes variantes de données en fonction de leur discriminant, les discriminated union types garantissent une manipulation sûre et précise des données dans les projets front-end. Ils facilitent également la maintenance du code en fournissant une documentation claire et en permettant à Typescript d'effectuer une vérification statique approfondie du code.

Pour conclure, les discriminated union types sont un outil essentiel pour les développeurs front-end Typescript qui cherchent à créer des applications robustes et maintenables. En les utilisant de manière judicieuse, les développeurs gagnent en lisibilité et en qualité de code pour leurs projets React et Next.js.

Nous confier votre projet

Présentez-nous votre projet , nous vous recontacterons dans les prochaines 24h.