Cours JavaScript et React · Module 8: Patterns React avancés

Accessibilité (a11y) en React

7 min de lecture
Dans cette leçon9

Sur une station spatiale, chaque membre d'équipage doit pouvoir accéder aux systèmes de contrôle - qu'il les utilise par la vue, le toucher ou des commandes vocales. L'accessibilité (a11y en abrégé) dans les applications web fonctionne exactement de la même manière. Quand on construit une interface React, on doit s'assurer que chaque utilisateur peut s'en servir - les personnes aveugles utilisant un lecteur d'écran, celles qui naviguent uniquement au clavier, ou celles ayant des déficiences de la vision des couleurs.

Pourquoi l'accessibilité est-elle importante ?

Selon l'OMS, plus d'un milliard de personnes dans le monde vivent avec une forme de handicap. Cela signifie qu'une application inaccessible exclut un énorme groupe d'utilisateurs potentiels. Au-delà de l'aspect éthique, l'accessibilité :

  • Est légalement requise dans de nombreux pays (WCAG, ADA)
  • Améliore le SEO et la qualité générale du code
  • Rend l'application plus facile à utiliser pour TOUS les utilisateurs (par exemple sur les appareils mobiles)
  • Rend l'interface plus prévisible et testable

HTML sémantique en React

Le fondement de l'accessibilité est l'utilisation des bons éléments HTML. React encourage la création de composants, mais il est facile de tomber dans le piège de la surutilisation des <div> :

1// MAUVAIS - « soupe de div » - le lecteur d'écran ne comprend pas la structure
2function BadNavigation() {
3  return (
4    <div className="nav">
5      <div className="nav-item" onClick={goHome}>Accueil</div>
6      <div className="nav-item" onClick={goMissions}>Missions</div>
7      <div className="nav-item" onClick={goCrewPanel}>Équipage</div>
8    </div>
9  );
10}
11
12// BON - éléments HTML sémantiques
13function GoodNavigation() {
14  return (
15    <nav aria-label="Menu principal de la station">
16      <ul>
17        <li><a href="/">Accueil</a></li>
18        <li><a href="/missions">Missions</a></li>
19        <li><a href="/crew">Équipage</a></li>
20      </ul>
21    </nav>
22  );
23}

Les éléments sémantiques clés incluent : <nav>, <main>, <header>, <footer>, <section>, <article>, <aside>, <button>, <form>, <label>.

Attributs ARIA en React

ARIA (Accessible Rich Internet Applications) est un ensemble d'attributs qui ajoutent des informations sur le rôle et l'état des éléments pour les technologies d'assistance. En React, nous les utilisons en camelCase ou avec des traits d'union :

1function MissionAlert({ message, severity }) {
2  return (
3    <div
4      role="alert"
5      aria-live="assertive"
6      aria-atomic="true"
7      className={severity}
8    >
9      {message}
10    </div>
11  );
12}
13
14function SearchField() {
15  const [results, setResults] = useState([]);
16  const [isOpen, setIsOpen] = useState(false);
17
18  return (
19    <div>
20      <label htmlFor="galaxy-search">Rechercher des galaxies :</label>
21      <input
22        id="galaxy-search"
23        type="search"
24        role="combobox"
25        aria-expanded={isOpen}
26        aria-controls="search-results"
27        aria-autocomplete="list"
28        aria-haspopup="listbox"
29      />
30      {isOpen && (
31        <ul id="search-results" role="listbox">
32          {results.map(r => (
33            <li key={r.id} role="option">{r.name}</li>
34          ))}
35        </ul>
36      )}
37    </div>
38  );
39}

Les attributs ARIA les plus couramment utilisés en React :

AttributCas d'usage
roleDéfinit le rôle de l'élément (alert, dialog, tabpanel)
aria-labelÉtiquette textuelle de l'élément
aria-labelledbyID de l'élément qui sert d'étiquette
aria-describedbyID de l'élément avec une description supplémentaire
aria-liveAnnonce les changements de contenu dynamiques
aria-expandedIndique si l'élément est déplié
aria-hiddenCache l'élément au lecteur d'écran
aria-disabledIndique un élément désactivé

Gestion du focus

Dans les applications SPA (Single Page Application), la gestion du focus clavier est critique. Quand un utilisateur navigue au clavier, le focus doit se déplacer dans un ordre logique :

1import { useRef, useEffect } from 'react';
2
3function MissionModal({ isOpen, onClose, title, children }) {
4  const modalRef = useRef(null);
5  const closeButtonRef = useRef(null);
6
7  // Déplacer le focus vers la modale après l'ouverture
8  useEffect(() => {
9    if (isOpen && closeButtonRef.current) {
10      closeButtonRef.current.focus();
11    }
12  }, [isOpen]);
13
14  // Focus trap - Tab ne quitte pas la modale
15  const handleKeyDown = (e) => {
16    if (e.key === 'Escape') {
17      onClose();
18    }
19
20    if (e.key === 'Tab') {
21      const focusableElements = modalRef.current.querySelectorAll(
22        'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
23      );
24      const firstElement = focusableElements[0];
25      const lastElement = focusableElements[focusableElements.length - 1];
26
27      if (e.shiftKey && document.activeElement === firstElement) {
28        e.preventDefault();
29        lastElement.focus();
30      } else if (!e.shiftKey && document.activeElement === lastElement) {
31        e.preventDefault();
32        firstElement.focus();
33      }
34    }
35  };
36
37  if (!isOpen) return null;
38
39  return (
40    <div
41      className="modal-overlay"
42      role="dialog"
43      aria-modal="true"
44      aria-labelledby="modal-title"
45      ref={modalRef}
46      onKeyDown={handleKeyDown}
47    >
48      <div className="modal-content">
49        <h2 id="modal-title">{title}</h2>
50        {children}
51        <button ref={closeButtonRef} onClick={onClose}>
52          Fermer
53        </button>
54      </div>
55    </div>
56  );
57}

Chaque élément interactif doit être accessible au clavier. React prend en charge la gestion des touches via onKeyDown, onKeyUp et onKeyPress :

1function SpaceshipSelector({ ships, onSelect }) {
2  const [activeIndex, setActiveIndex] = useState(0);
3
4  const handleKeyDown = (e) => {
5    switch (e.key) {
6      case 'ArrowDown':
7        e.preventDefault();
8        setActiveIndex(prev => Math.min(prev + 1, ships.length - 1));
9        break;
10      case 'ArrowUp':
11        e.preventDefault();
12        setActiveIndex(prev => Math.max(prev - 1, 0));
13        break;
14      case 'Enter':
15      case ' ':
16        e.preventDefault();
17        onSelect(ships[activeIndex]);
18        break;
19    }
20  };
21
22  return (
23    <ul
24      role="listbox"
25      aria-label="Sélectionner un vaisseau"
26      tabIndex={0}
27      onKeyDown={handleKeyDown}
28    >
29      {ships.map((ship, index) => (
30        <li
31          key={ship.id}
32          role="option"
33          aria-selected={index === activeIndex}
34          onClick={() => onSelect(ship)}
35          style={{
36            background: index === activeIndex ? '#1a3a5c' : 'transparent',
37          }}
38        >
39          {ship.name} - {ship.type}
40        </li>
41      ))}
42    </ul>
43  );
44}

Annoncer les changements dynamiques (Live Regions)

Quand le contenu de la page change dynamiquement (par exemple, une notification apparaît), le lecteur d'écran doit en être informé. Nous utilisons aria-live :

1function MissionStatusBoard() {
2  const [status, setStatus] = useState('En attente du lancement');
3  const [logs, setLogs] = useState([]);
4
5  const updateStatus = (newStatus) => {
6    setStatus(newStatus);
7    setLogs(prev => [...prev, newStatus]);
8  };
9
10  return (
11    <div>
12      {/* aria-live="polite" - attend que le lecteur termine le texte en cours */}
13      <div aria-live="polite" aria-atomic="true">
14        <h2>Statut de la mission : {status}</h2>
15      </div>
16
17      {/* aria-live="assertive" - interrompt la lecture en cours */}
18      <div aria-live="assertive" className="sr-only">
19        {logs.length > 0 && logs[logs.length - 1]}
20      </div>
21
22      <button onClick={() => updateStatus('Compte à rebours démarré')}>
23        Démarrer le compte à rebours
24      </button>
25      <button onClick={() => updateStatus('Lancement !')}>
26        Lancer
27      </button>
28    </div>
29  );
30}

La classe sr-only (screen reader only)

Parfois nous avons besoin de texte visible uniquement par les lecteurs d'écran :

1// Style CSS pour cacher visuellement, mais pas aux lecteurs d'écran
2const srOnlyStyle = {
3  position: 'absolute',
4  width: '1px',
5  height: '1px',
6  padding: 0,
7  margin: '-1px',
8  overflow: 'hidden',
9  clip: 'rect(0, 0, 0, 0)',
10  whiteSpace: 'nowrap',
11  borderWidth: 0,
12};
13
14function IconButton({ icon, label, onClick }) {
15  return (
16    <button onClick={onClick} aria-label={label}>
17      <span aria-hidden="true">{icon}</span>
18      <span style={srOnlyStyle}>{label}</span>
19    </button>
20  );
21}
22
23// Utilisation
24<IconButton icon="▲" label="Démarrer la mission" onClick={startMission} />

Formulaires accessibles

Les formulaires sont l'un des domaines les plus importants de l'accessibilité. Chaque champ doit avoir une étiquette, et les erreurs doivent être clairement communiquées :

1function CrewRegistrationForm() {
2  const [errors, setErrors] = useState({});
3
4  return (
5    <form aria-label="Inscription d'un membre d'équipage">
6      <div>
7        <label htmlFor="crew-name">Nom complet :</label>
8        <input
9          id="crew-name"
10          type="text"
11          aria-required="true"
12          aria-invalid={!!errors.name}
13          aria-describedby={errors.name ? 'name-error' : undefined}
14        />
15        {errors.name && (
16          <span id="name-error" role="alert" style={{ color: 'red' }}>
17            {errors.name}
18          </span>
19        )}
20      </div>
21
22      <fieldset>
23        <legend>Spécialisation :</legend>
24        <label>
25          <input type="radio" name="role" value="pilot" />
26          Pilote
27        </label>
28        <label>
29          <input type="radio" name="role" value="engineer" />
30          Ingénieur
31        </label>
32        <label>
33          <input type="radio" name="role" value="scientist" />
34          Scientifique
35        </label>
36      </fieldset>
37
38      <button type="submit">Rejoindre l'équipage</button>
39    </form>
40  );
41}

Résumé

L'accessibilité en React n'est pas un ajout, mais une partie intégrante du processus de création d'interface :

  • HTML sémantique est le fondement - utilise <nav>, <main>, <button> au lieu de <div> pour tout
  • Attributs ARIA complètent la sémantique quand les éléments natifs ne suffisent pas
  • Gestion du focus est critique dans les SPA - une modale doit « piéger » le focus, la navigation doit être logique
  • Navigation au clavier - chaque élément interactif doit être accessible sans souris
  • Live regions (aria-live) annoncent les changements de contenu dynamiques
  • Formulaires nécessitent des étiquettes, des descriptions d'erreurs et des rôles appropriés

Tout comme les systèmes d'une station spatiale doivent être conçus pour que chaque membre d'équipage puisse les utiliser en toutes conditions - même lorsque les lumières s'éteignent et que l'écran tactile cesse de fonctionner - une application React accessible garantit que chaque utilisateur peut l'utiliser efficacement.

Code de cette leçon : App.jsx
1import React, { useState, useRef, useEffect } from 'react';
2
3// ============================================
4// DEMO : Accessibilite (a11y) en React
5// ============================================
6
7// --- Composant 1 : Navigation avec ARIA ---
8function SpaceNavigation() {
9  return (
10    <nav aria-label="Menu principal de la station spatiale">
11      <ul style={navListStyle}>
12        {[
13          { label: 'Panneau de contrôle', icon: '' },
14          { label: 'Carte des étoiles', icon: '' },
15          { label: 'Équipage', icon: '' },
16          { label: 'Systèmes', icon: '' },
17        ].map(item => (
18          <li key={item.label}>
19            <button
20              style={navBtnStyle}
21              aria-label={item.label}
22              onClick={() => alert(item.label)}
23            >
24              <span aria-hidden="true">{item.icon}</span>
25              <span>{item.label}</span>
26            </button>
27          </li>
28        ))}
29      </ul>
30    </nav>
31  );
32}
33
34// --- Composant 2 : Modale avec focus trap ---
35function AccessibleModal({ isOpen, onClose, title, children }) {
36  const modalRef = useRef(null);
37  const closeRef = useRef(null);
38
39  useEffect(() => {
40    if (isOpen && closeRef.current) {
41      closeRef.current.focus();
42    }
43  }, [isOpen]);
44
45  const handleKeyDown = (e) => {
46    if (e.key === 'Escape') onClose();
47
48    if (e.key === 'Tab' && modalRef.current) {
49      const focusable = modalRef.current.querySelectorAll(
50        'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
51      );
52      const first = focusable[0];
53      const last = focusable[focusable.length - 1];
54
55      if (e.shiftKey && document.activeElement === first) {
56        e.preventDefault();
57        last.focus();
58      } else if (!e.shiftKey && document.activeElement === last) {
59        e.preventDefault();
60        first.focus();
61      }
62    }
63  };
64
65  if (!isOpen) return null;
66
67  return (
68    <div style={overlayStyle} onClick={onClose}>
69      <div
70        ref={modalRef}
71        role="dialog"
72        aria-modal="true"
73        aria-labelledby="modal-title"
74        style={modalStyle}
75        onClick={(e) => e.stopPropagation()}
76        onKeyDown={handleKeyDown}
77      >
78        <h2 id="modal-title" style={{ color: '#00d4ff', marginBottom: '12px' }}>
79          {title}
80        </h2>
81        {children}
82        <button ref={closeRef} onClick={onClose} style={closeBtnStyle}>
83          Fermer (Esc)
84        </button>
85      </div>
86    </div>
87  );
88}
89
90// --- Composant 3 : Liste avec navigation au clavier ---
91function ShipSelector() {
92  const ships = [
93    { id: 1, name: 'Aurora', type: 'Cruiser', crew: 120 },
94    { id: 2, name: 'Nebula', type: 'Frégate', crew: 45 },
95    { id: 3, name: 'Vortex', type: 'Fighter', crew: 2 },
96    { id: 4, name: 'Horizon', type: 'Transporter', crew: 300 },
97    { id: 5, name: 'Eclipse', type: 'Éclaireur', crew: 8 },
98  ];
99
100  const [activeIndex, setActiveIndex] = useState(0);
101  const [selected, setSelected] = useState(null);
102
103  const handleKeyDown = (e) => {
104    switch (e.key) {
105      case 'ArrowDown':
106        e.preventDefault();
107        setActiveIndex(prev => Math.min(prev + 1, ships.length - 1));
108        break;
109      case 'ArrowUp':
110        e.preventDefault();
111        setActiveIndex(prev => Math.max(prev - 1, 0));
112        break;
113      case 'Enter':
114      case ' ':
115        e.preventDefault();
116        setSelected(ships[activeIndex]);
117        break;
118    }
119  };
120
121  return (
122    <div>
123      <h3 style={headingStyle}>Choisis un vaisseau (navigue avec les flèches)</h3>
124      <ul
125        role="listbox"
126        aria-label="Liste des vaisseaux spatiaux"
127        tabIndex={0}
128        onKeyDown={handleKeyDown}
129        style={{ listStyle: 'none', padding: 0, outline: 'none' }}
130      >
131        {ships.map((ship, index) => (
132          <li
133            key={ship.id}
134            role="option"
135            aria-selected={index === activeIndex}
136            onClick={() => { setActiveIndex(index); setSelected(ship); }}
137            style={{
138              padding: '10px 14px',
139              background: index === activeIndex ? '#1a3a5c' : '#0d1b2a',
140              borderRadius: '6px',
141              marginBottom: '4px',
142              cursor: 'pointer',
143              border: index === activeIndex ? '1px solid #00d4ff' : '1px solid transparent',
144              display: 'flex',
145              justifyContent: 'space-between',
146            }}
147          >
148            <span>{ship.name} - {ship.type}</span>
149            <span style={{ opacity: 0.5 }}>Équipage : {ship.crew}</span>
150          </li>
151        ))}
152      </ul>
153      {selected && (
154        <div aria-live="polite" style={selectedStyle}>
155          Vaisseau sélectionné : <strong>{selected.name}</strong> ({selected.type})
156        </div>
157      )}
158    </div>
159  );
160}
161
162// --- Composant 4 : Formulaire avec validation ---
163function CrewForm() {
164  const [name, setName] = useState('');
165  const [role, setRole] = useState('');
166  const [errors, setErrors] = useState({});
167  const [submitted, setSubmitted] = useState(false);
168
169  const validate = () => {
170    const newErrors = {};
171    if (!name.trim()) newErrors.name = 'Le nom est requis';
172    if (!role) newErrors.role = 'Choisis une spécialisation';
173    setErrors(newErrors);
174    return Object.keys(newErrors).length === 0;
175  };
176
177  const handleSubmit = (e) => {
178    e.preventDefault();
179    if (validate()) {
180      setSubmitted(true);
181      setName('');
182      setRole('');
183      setTimeout(() => setSubmitted(false), 3000);
184    }
185  };
186
187  return (
188    <form onSubmit={handleSubmit} aria-label="Inscription d'un membre de l'équipage">
189      <h3 style={headingStyle}>Inscription de l'équipage</h3>
190
191      <div style={{ marginBottom: '12px' }}>
192        <label htmlFor="crew-name" style={labelStyle}>Nom et prénom :</label>
193        <input
194          id="crew-name"
195          type="text"
196          value={name}
197          onChange={(e) => setName(e.target.value)}
198          aria-required="true"
199          aria-invalid={!!errors.name}
200          aria-describedby={errors.name ? 'name-error' : undefined}
201          style={inputStyle}
202        />
203        {errors.name && (
204          <span id="name-error" role="alert" style={errorStyle}>
205            {errors.name}
206          </span>
207        )}
208      </div>
209
210      <fieldset style={{ border: '1px solid #1a3a5c', borderRadius: '6px', padding: '12px', marginBottom: '12px' }}>
211        <legend style={{ color: '#00d4ff', padding: '0 8px' }}>Spécialisation :</legend>
212        {['pilote', 'ingénieur', 'scientifique', 'médecin'].map(r => (
213          <label key={r} style={{ display: 'block', marginBottom: '6px', cursor: 'pointer' }}>
214            <input
215              type="radio"
216              name="role"
217              value={r}
218              checked={role === r}
219              onChange={(e) => setRole(e.target.value)}
220              style={{ marginRight: '8px' }}
221            />
222            {r.charAt(0).toUpperCase() + r.slice(1)}
223          </label>
224        ))}
225        {errors.role && (
226          <span role="alert" style={errorStyle}>{errors.role}</span>
227        )}
228      </fieldset>
229
230      <button type="submit" style={submitBtnStyle}>
231        Rejoindre l'équipage
232      </button>
233
234      {submitted && (
235        <div aria-live="assertive" role="alert" style={successStyle}>
236          Inscription terminée avec succès !
237        </div>
238      )}
239    </form>
240  );
241}
242
243// --- Composant 5 : Live region ---
244function MissionStatus() {
245  const [status, setStatus] = useState('En attente');
246  const statuses = ['En attente', 'Compte à rebours', 'Décollage !', 'En vol', 'Orbite atteinte'];
247
248  return (
249    <div>
250      <h3 style={headingStyle}>Statut de la mission (aria-live)</h3>
251      <div aria-live="polite" aria-atomic="true" style={statusBoxStyle}>
252        Status: <strong style={{ color: '#ffd700' }}>{status}</strong>
253      </div>
254      <div style={{ display: 'flex', gap: '6px', marginTop: '8px', flexWrap: 'wrap' }}>
255        {statuses.map(s => (
256          <button
257            key={s}
258            onClick={() => setStatus(s)}
259            style={{
260              ...navBtnStyle,
261              background: status === s ? '#00d4ff' : '#1a2a3a',
262              color: status === s ? '#000' : '#fff',
263            }}
264          >
265            {s}
266          </button>
267        ))}
268      </div>
269    </div>
270  );
271}
272
273// --- Styles ---
274const navListStyle = { listStyle: 'none', padding: 0, display: 'flex', gap: '6px', flexWrap: 'wrap' };
275const navBtnStyle = {
276  padding: '8px 14px', background: '#1a2a3a', border: 'none',
277  borderRadius: '6px', color: '#fff', cursor: 'pointer', fontSize: '13px',
278  display: 'flex', gap: '6px', alignItems: 'center',
279};
280const overlayStyle = {
281  position: 'fixed', top: 0, left: 0, right: 0, bottom: 0,
282  background: 'rgba(0,0,0,0.7)', display: 'flex',
283  alignItems: 'center', justifyContent: 'center', zIndex: 1000,
284};
285const modalStyle = {
286  background: '#0d1b2a', borderRadius: '12px', padding: '24px',
287  maxWidth: '400px', width: '90%', border: '1px solid #1a3a5c',
288};
289const closeBtnStyle = {
290  marginTop: '16px', padding: '8px 16px', background: '#f44336',
291  border: 'none', borderRadius: '6px', color: '#fff', cursor: 'pointer',
292};
293const headingStyle = { color: '#00d4ff', marginBottom: '10px', fontSize: '15px' };
294const inputStyle = {
295  width: '100%', padding: '8px', background: '#0d1b2a',
296  border: '1px solid #1a3a5c', borderRadius: '6px', color: '#fff',
297  fontSize: '14px', boxSizing: 'border-box',
298};
299const labelStyle = { display: 'block', marginBottom: '4px', color: '#00d4ff', fontSize: '13px' };
300const errorStyle = { color: '#f44336', fontSize: '12px', display: 'block', marginTop: '4px' };
301const submitBtnStyle = {
302  padding: '10px 20px', background: '#4CAF50', border: 'none',
303  borderRadius: '6px', color: '#fff', cursor: 'pointer', fontSize: '14px',
304};
305const successStyle = {
306  marginTop: '12px', padding: '10px', background: '#1a3a1a',
307  borderRadius: '6px', color: '#4CAF50', border: '1px solid #4CAF50',
308};
309const selectedStyle = {
310  marginTop: '8px', padding: '10px', background: '#1a2a3a',
311  borderRadius: '6px', border: '1px solid #00d4ff',
312};
313const statusBoxStyle = {
314  padding: '12px', background: '#0d1b2a', borderRadius: '6px',
315  border: '1px solid #1a3a5c',
316};
317const sectionStyle = {
318  marginBottom: '24px', padding: '16px',
319  background: 'rgba(255,255,255,0.03)', borderRadius: '12px',
320  border: '1px solid rgba(255,255,255,0.08)',
321};
322
323function App() {
324  const [modalOpen, setModalOpen] = useState(false);
325
326  return (
327    <div style={{ padding: '20px', background: '#0a0e1a', color: '#e0e0e0', minHeight: '100vh', fontFamily: 'system-ui' }}>
328      <h1 style={{ color: '#ffd700', textAlign: 'center', marginBottom: '6px' }}>
329        Accessibility (a11y) en React
330      </h1>
331      <p style={{ textAlign: 'center', opacity: 0.6, marginBottom: '24px', fontSize: '13px' }}>
332        Teste Tab, flèches, Enter, Esc
333      </p>
334
335      <section style={sectionStyle}>
336        <h2 style={{ color: '#00d4ff', marginBottom: '10px' }}>Navigation sémantique</h2>
337        <SpaceNavigation />
338      </section>
339
340      <section style={sectionStyle}>
341        <h2 style={{ color: '#00d4ff', marginBottom: '10px' }}>Modale avec focus trap</h2>
342        <button onClick={() => setModalOpen(true)} style={navBtnStyle}>
343          Ouvrir la modale de mission
344        </button>
345        <AccessibleModal
346          isOpen={modalOpen}
347          onClose={() => setModalOpen(false)}
348          title="Détails de la mission Alpha"
349        >
350          <p>Objectif : Exploration du système Kepler-442.</p>
351          <p>Durée : 18 mois.</p>
352          <input placeholder="Ajouter une note..." style={{ ...inputStyle, marginTop: '8px' }} />
353        </AccessibleModal>
354      </section>
355
356      <section style={sectionStyle}>
357        <ShipSelector />
358      </section>
359
360      <section style={sectionStyle}>
361        <MissionStatus />
362      </section>
363
364      <section style={sectionStyle}>
365        <CrewForm />
366      </section>
367    </div>
368  );
369}
370
371export default App;

Tu as repéré une erreur dans cette leçon ?

Teste tes connaissances

Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.

  1. 1. Quel est le but principal du pattern Adapter en React ?

  2. 2. Comment implémente-t-on le plus souvent le pattern Facade en React ?

Voici 2 des 4 questions de cette leçon. Réponds aux autres dans le jeu.

Tâches pratiques dans le jeu

  • Clique dans l'ordre

    Ordonne les attributs d'accessibilité d'un champ de saisie avec une erreur de validation :

  • Éditeur de code

    Liste virtualisée avec optimisations

  • Clique dans l'ordre

    Ordonne la syntaxe de React.forwardRef :

  • Tri vertical

    Ordonne l'implémentation du HOC :

  • Éditeur de code

    Error Boundary avancé

Articles utiles