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

Code splitting

11 min de lecture
Dans cette leçon14

À mesure que notre application React cosmique grandit, le fichier JavaScript qui se charge dans le navigateur grossit aussi. Cela peut entraîner des temps de chargement plus longs, une utilisation mémoire plus élevée et, de manière générale, de moins bonnes performances. Le code splitting est une technique qui permet de « découper » l'application en plus petites parties pouvant être chargées dynamiquement, uniquement lorsqu'elles sont nécessaires.

Qu'est-ce que le code splitting ?

Le code splitting est une technique qui divise un gros bundle JavaScript en plus petits chunks pouvant être chargés à la demande ou en parallèle. Au lieu d'envoyer toute l'application à l'utilisateur d'un coup, nous n'envoyons que ce qui est nécessaire au rendu initial, et le reste est chargé en arrière-plan ou au besoin.

C'est comme lancer une mission spatiale - nous n'envoyons pas toutes les ressources d'un coup, mais par étapes : d'abord le vaisseau principal, puis les modules supplémentaires, et enfin les outils et équipements plus petits lorsqu'ils deviennent nécessaires.

Pourquoi avons-nous besoin du code splitting ?

Imagine que nous créons une application spatiale avec de nombreux modules :

  • Panneau de contrôle de mission
  • Système de surveillance des astronautes
  • Carte de la galaxie
  • Encyclopédie des planètes découvertes
  • Simulateur de vol
  • Centre de communication interstellaire

Si nous construisons toute l'application en un seul bundle, l'utilisateur doit télécharger tout ce code même s'il veut simplement consulter la carte de la galaxie. En conséquence :

  • Temps de chargement initial de la page plus long
  • Utilisation mémoire du navigateur plus élevée
  • Coûts de transfert de données plus élevés (particulièrement important sur les appareils mobiles)
  • Chargement et parsing inutiles de code non utilisé

Avantages du code splitting

  • Chargement initial plus rapide - temps d'interactivité plus court (TTI - Time To Interactive)
  • Meilleures performances sur les appareils mobiles - utilisation mémoire et CPU plus faible
  • Priorisation du chemin critique - les parties les plus importantes de l'application se chargent en premier
  • Meilleur caching - les plus petites parties sont plus faciles à mettre en cache et à mettre à jour
  • Meilleure expérience utilisateur - l'application devient réactive plus rapidement

Implémenter le code splitting en React

React prend en charge le code splitting via les imports dynamiques et le composant React.lazy. Voyons comment cela fonctionne :

1. Import dynamique

La fonction import() est une fonction ES standard qui permet d'importer dynamiquement des modules. Au lieu de charger toutes les dépendances en haut du fichier, nous pouvons les charger uniquement lorsqu'elles sont nécessaires :

1// Sans code splitting
2import { calculateOrbit } from './complexOrbitCalculations';
3
4function OrbitCalculator({ planetData }) {
5  const orbitInfo = calculateOrbit(planetData);
6  return <div>{/* Rendu des résultats */}</div>;
7}
8
9// Avec code splitting
10function OrbitCalculator({ planetData }) {
11  const [orbitInfo, setOrbitInfo] = useState(null);
12  const [loading, setLoading] = useState(true);
13
14  useEffect(() => {
15    // Import dynamique du module uniquement au montage du composant
16    import('./complexOrbitCalculations')
17      .then(module => {
18        const result = module.calculateOrbit(planetData);
19        setOrbitInfo(result);
20        setLoading(false);
21      });
22  }, [planetData]);
23
24  if (loading) return <p>Calcul de l'orbite...</p>;
25  return <div>{/* Rendu des résultats */}</div>;
26}

2. React.lazy

React.lazy est une fonction qui permet de rendre un import dynamique comme un composant ordinaire. Elle ne fonctionne qu'avec les exports par défaut (default).

1import React, { Suspense } from 'react';
2
3// Import classique
4// import GalaxyMap from './GalaxyMap';
5
6// Import lazy
7const GalaxyMap = React.lazy(() => import('./GalaxyMap'));
8const PlanetDetails = React.lazy(() => import('./PlanetDetails'));
9const SpaceshipControls = React.lazy(() => import('./SpaceshipControls'));
10
11function SpaceExplorer() {
12  const [view, setView] = useState('map');
13
14  return (
15    <div className="space-explorer">
16      <nav>
17        <button onClick={() => setView('map')}>Carte de la galaxie</button>
18        <button onClick={() => setView('planets')}>Détails des planètes</button>
19        <button onClick={() => setView('controls')}>Panneau de contrôle</button>
20      </nav>
21
22      <Suspense fallback={<div>Chargement du module...</div>}>
23        {view === 'map' && <GalaxyMap />}
24        {view === 'planets' && <PlanetDetails />}
25        {view === 'controls' && <SpaceshipControls />}
26      </Suspense>
27    </div>
28  );
29}

Dans l'exemple ci-dessus :

  1. Nous utilisons React.lazy pour importer dynamiquement les composants
  2. Nous enveloppons les composants dans Suspense pour afficher un fallback pendant le chargement
  3. Les composants ne sont chargés que lorsqu'ils sont nécessaires (quand l'utilisateur change de vue)

Suspense

Suspense est un composant React qui permet de « suspendre » le rendu jusqu'à ce que certaines conditions soient remplies (par exemple, données chargées ou code du composant disponible).

Suspense accepte une prop fallback, qui est rendue pendant l'attente :

1<Suspense fallback={<LoadingSpinner message="Préparation du module..." />}>
2  <LazilyLoadedComponent />
3</Suspense>

Suspense peut gérer plusieurs composants Lazy à la fois :

1<Suspense fallback={<LoadingSpinner />}>
2  <LazyComponent1 />
3  <div>
4    <LazyComponent2 />
5  </div>
6  <LazyComponent3 />
7</Suspense>

Imbriquer Suspense

Nous pouvons aussi imbriquer les composants Suspense pour obtenir un contrôle plus granulaire sur ce qui est affiché pendant le chargement :

1function SpaceStation() {
2  return (
3    <div>
4      <h1>Station spatiale Alpha</h1>
5
6      {/* Le layout principal se charge immédiatement */}
7      <StationLayout />
8
9      {/* La carte se charge avec son propre fallback */}
10      <Suspense fallback={<p>Chargement de la carte de la station...</p>}>
11        <StationMap />
12      </Suspense>
13
14      {/* Le panneau de contrôle a des composants lazy imbriqués */}
15      <Suspense fallback={<p>Préparation du panneau de contrôle...</p>}>
16        <ControlPanel>
17          <Suspense fallback={<p>Chargement des systèmes de support vital...</p>}>
18            <LifeSupportMonitor />
19          </Suspense>
20
21          <Suspense fallback={<p>Chargement des commandes des moteurs...</p>}>
22            <EngineControls />
23          </Suspense>
24        </ControlPanel>
25      </Suspense>
26    </div>
27  );
28}

Code splitting basé sur les routes

L'une des manières les plus courantes d'appliquer le code splitting est de diviser le code en fonction des routes. Chaque page ou vue devient un chunk de code distinct :

1import React, { Suspense, lazy } from 'react';
2import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
3import Navbar from './Navbar';
4import LoadingPage from './LoadingPage';
5
6// Lazy loading des pages
7const MissionControl = lazy(() => import('./pages/MissionControl'));
8const GalaxyMap = lazy(() => import('./pages/GalaxyMap'));
9const CrewManagement = lazy(() => import('./pages/CrewManagement'));
10const ResearchDatabase = lazy(() => import('./pages/ResearchDatabase'));
11const SpaceshipConfig = lazy(() => import('./pages/SpaceshipConfig'));
12
13function App() {
14  return (
15    <Router>
16      <Navbar />
17      <Suspense fallback={<LoadingPage />}>
18        <Switch>
19          <Route exact path="/" component={MissionControl} />
20          <Route path="/galaxy-map" component={GalaxyMap} />
21          <Route path="/crew" component={CrewManagement} />
22          <Route path="/research" component={ResearchDatabase} />
23          <Route path="/spaceship" component={SpaceshipConfig} />
24        </Switch>
25      </Suspense>
26    </Router>
27  );
28}

Code splitting des bibliothèques

Nous pouvons aussi appliquer le code splitting à de grandes bibliothèques qui ne sont pas nécessaires immédiatement :

1import React, { useState } from 'react';
2
3function DataVisualizer({ data }) {
4  const [charts, setCharts] = useState(null);
5  const [showCharts, setShowCharts] = useState(false);
6
7  const loadChartsLibrary = async () => {
8    // Charger la bibliothèque uniquement quand l'utilisateur veut voir les graphiques
9    const D3Module = await import('d3');
10    const ChartModule = await import('./ChartComponent');
11
12    setCharts({
13      d3: D3Module.default,
14      ChartComponent: ChartModule.default
15    });
16  };
17
18  const handleShowCharts = () => {
19    if (!charts) {
20      setShowCharts(true);
21      loadChartsLibrary();
22    } else {
23      setShowCharts(true);
24    }
25  };
26
27  return (
28    <div className="data-visualizer">
29      <h2>Données d'exploration spatiale</h2>
30      <button onClick={handleShowCharts}>
31        {showCharts ? 'Masquer les graphiques' : 'Afficher les graphiques'}
32      </button>
33
34      {showCharts && (
35        <>
36          {charts ? (
37            <charts.ChartComponent data={data} d3={charts.d3} />
38          ) : (
39            <p>Chargement des graphiques avancés...</p>
40          )}
41        </>
42      )}
43    </div>
44  );
45}

Preloading

Parfois nous savons qu'un composant sera bientôt nécessaire (par exemple, quand l'utilisateur survole un bouton qui affichera une modale). Nous pouvons commencer à charger le module plus tôt :

1const PlanetExplorer = React.lazy(() => import('./PlanetExplorer'));
2
3function PlanetInfoButton({ planetId }) {
4  const [showExplorer, setShowExplorer] = useState(false);
5
6  const handleMouseEnter = () => {
7    // Commencer à télécharger le module quand l'utilisateur survole le bouton
8    const PlanetExplorerModule = import('./PlanetExplorer');
9  };
10
11  return (
12    <>
13      <button
14        onMouseEnter={handleMouseEnter}
15        onClick={() => setShowExplorer(true)}
16      >
17        Explorer la planète {planetId}
18      </button>
19
20      {showExplorer && (
21        <Suspense fallback={<p>Préparation de l'explorateur...</p>}>
22          <PlanetExplorer planetId={planetId} />
23        </Suspense>
24      )}
25    </>
26  );
27}

Named exports et code splitting

React.lazy ne fonctionne qu'avec les exports par défaut. Si nous voulons utiliser des named exports, nous devons créer un module intermédiaire :

1// MathUtils.js
2export const calculateDistance = (a, b) => { /* ... */ };
3export const calculateVelocity = (distance, time) => { /* ... */ };
4export const calculateAcceleration = (velocity, time) => { /* ... */ };
5
6// DistanceCalculator.js (module intermédiaire)
7export { calculateDistance } from './MathUtils';
8export default calculateDistance;
9
10// Utilisation
11const DistanceCalculator = React.lazy(() => import('./DistanceCalculator'));

Alternativement, nous pouvons gérer l'import manuellement et extraire l'export souhaité :

1const calculateVelocity = React.lazy(() =>
2  import('./MathUtils').then(module => ({ default: module.calculateVelocity }))
3);

Stratégies de code splitting

1. Découpage basé sur les routes

L'approche la plus courante et la plus simple - chaque page est un chunk de code distinct.

2. Découpage basé sur les fonctionnalités

Les composants liés à une fonctionnalité spécifique (par exemple, panneau d'administration, analytique avancée) sont regroupés ensemble.

1// Chargement de tout le module admin à la demande
2const AdminDashboard = React.lazy(() => import('./admin/Dashboard'));
3
4function App() {
5  const [isAdmin, setIsAdmin] = useState(false);
6
7  // isAdmin est défini après la connexion de l'utilisateur
8
9  return (
10    <div>
11      {isAdmin ? (
12        <Suspense fallback={<p>Chargement du panneau admin...</p>}>
13          <AdminDashboard />
14        </Suspense>
15      ) : (
16        <UserDashboard />
17      )}
18    </div>
19  );
20}

3. Découpage basé sur la priorité

Composants critiques chargés immédiatement, moins importants chargés en lazy.

1// Composants critiques importés normalement
2import CriticalSystemStatus from './CriticalSystemStatus';
3import NavigationControls from './NavigationControls';
4
5// Composants moins importants chargés en lazy
6const SystemLogs = React.lazy(() => import('./SystemLogs'));
7const CrewSchedule = React.lazy(() => import('./CrewSchedule'));
8const MaintenanceStatus = React.lazy(() => import('./MaintenanceStatus'));
9
10function SpaceshipDashboard() {
11  return (
12    <div className="dashboard">
13      <div className="critical-section">
14        <CriticalSystemStatus />
15        <NavigationControls />
16      </div>
17
18      <Suspense fallback={<p>Chargement des modules supplémentaires...</p>}>
19        <div className="secondary-section">
20          <SystemLogs />
21          <CrewSchedule />
22          <MaintenanceStatus />
23        </div>
24      </Suspense>
25    </div>
26  );
27}

4. Vendor splitting

Découpage de grandes bibliothèques externes en chunks distincts.

Dans webpack, cela peut être réalisé grâce à la configuration splitChunks :

1module.exports = {
2  //...
3  optimization: {
4    splitChunks: {
5      chunks: 'all',
6      maxInitialRequests: Infinity,
7      minSize: 0,
8      cacheGroups: {
9        vendor: {
10          test: /[\/]node_modules[\/]/,
11          name(module) {
12            // get the name. E.g. node_modules/packageName/sub/path
13            // or node_modules/packageName
14            const packageName = module.context.match(
15              /[\/]node_modules[\/](.*?)([\/]|$)/
16            )[1];
17
18            // npm package names are URL-safe, but some servers don't like @ symbols
19            return `npm.${packageName.replace('@', '')}`;
20          },
21        },
22      },
23    },
24  },
25};

Analyser et déboguer le code splitting

Analyser la taille des bundles

Des outils comme « webpack-bundle-analyzer » ou « source-map-explorer » aident à visualiser le contenu des bundles et à identifier les opportunités de découpage :

1# Installation
2npm install --save-dev webpack-bundle-analyzer
3
4# Configuration dans webpack.config.js
5const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
6
7module.exports = {
8  plugins: [
9    new BundleAnalyzerPlugin()
10  ]
11}

Déboguer les problèmes de code splitting

Problèmes courants de code splitting et leurs solutions :

  1. Les composants clignotent pendant le chargement

    • Solution : Conçois le fallback pour qu'il corresponde au layout final du composant
  2. Trop de petits chunks

    • Solution : Regroupe les composants liés dans un seul import dynamique
  3. Problèmes de SSR (Server-Side Rendering)

    • Solution : Utilise loadable-components au lieu de React.lazy pour le SSR

Bonnes pratiques de code splitting

  1. N'abuse pas du découpage - trop de petits chunks peut entraîner des problèmes de performance dus aux nombreuses requêtes HTTP

  2. Analyse tes bundles - surveille régulièrement la taille et le contenu des bundles

  3. Utilise les Magic Comments de webpack pour un meilleur contrôle des chunks :

    1// Named chunk
    2const HomeComponent = React.lazy(() => import(
    3  /* webpackChunkName: "home" */
    4  './HomeComponent'
    5));
    6
    7// Prefetch - suggère au navigateur que cette ressource sera nécessaire
    8const SettingsComponent = React.lazy(() => import(
    9  /* webpackChunkName: "settings" */
    10  /* webpackPrefetch: true */
    11  './SettingsComponent'
    12));
    13
    14// Preload - charge cette ressource avec une haute priorité
    15const ProfileComponent = React.lazy(() => import(
    16  /* webpackChunkName: "profile" */
    17  /* webpackPreload: true */
    18  './ProfileComponent'
    19));
  4. Utilise le code splitting avec le caching - des en-têtes de cache bien configurés pour les chunks peuvent considérablement améliorer les performances

  5. Surveille l'impact sur les performances - utilise des outils comme Lighthouse pour mesurer les temps de chargement et le TTI (Time To Interactive)

Code splitting et TypeScript

TypeScript fonctionne bien avec le code splitting mais nécessite quelques étapes supplémentaires :

1// Assure-toi que tsconfig.json contient :
2// "module": "esnext"
3
4// Import dynamique avec typage
5const DataVisualization = React.lazy(() =>
6  import('./DataVisualization') as Promise<{ default: React.ComponentType<DataVisProps> }>
7);
8
9interface DataVisProps {
10  data: Array<DataPoint>;
11  width: number;
12  height: number;
13}
14
15interface DataPoint {
16  x: number;
17  y: number;
18  label: string;
19}
20
21function Dashboard() {
22  const [data, setData] = useState<Array<DataPoint>>([]);
23
24  return (
25    <div>
26      <h1>Panneau de mission spatiale</h1>
27
28      <Suspense fallback={<div>Chargement de la visualisation...</div>}>
29        <DataVisualization
30          data={data}
31          width={800}
32          height={400}
33        />
34      </Suspense>
35    </div>
36  );
37}

Résumé

Le code splitting est une puissante technique d'optimisation qui permet d'améliorer considérablement les performances des applications React, surtout pour les projets plus importants. Grâce au code splitting, les utilisateurs ne chargent que ce dont ils ont besoin, ce qui conduit à un démarrage plus rapide de l'application et à une meilleure expérience utilisateur.

Les outils et méthodes clés incluent :

  • React.lazy() et Suspense pour les composants
  • import() dynamique pour les modules
  • Stratégies de découpage basées sur les routes, les fonctionnalités ou la priorité
  • Outils d'analyse de bundles pour aider à la prise de décision

En implémentant le code splitting dans notre application cosmique, nous pouvons garantir que les utilisateurs accèdent rapidement aux fonctionnalités les plus importantes, et que les ressources supplémentaires sont chargées lorsqu'elles sont nécessaires - à l'image d'une mission spatiale à plusieurs étapes où chaque module est livré au bon moment.

Code de cette leçon : App.jsx
1import React, { createContext, useContext, useState } from 'react';
2
3// ============================================
4// PATTERN : Compound Components
5// ============================================
6// Composants partageant l'etat via le Context
7
8// Context pour Accordion
9const AccordionContext = createContext();
10
11function useAccordion() {
12  const context = useContext(AccordionContext);
13  if (!context) {
14    throw new Error('Accordion components must be used within <Accordion>');
15  }
16  return context;
17}
18
19// Composant Accordion principal
20function Accordion({ children, defaultOpen = null }) {
21  const [openItem, setOpenItem] = useState(defaultOpen);
22
23  const toggle = (id) => {
24    setOpenItem(prev => prev === id ? null : id);
25  };
26
27  return (
28    <AccordionContext.Provider value={{ openItem, toggle }}>
29      <div style={styles.accordion}>
30        {children}
31      </div>
32    </AccordionContext.Provider>
33  );
34}
35
36// Sous-composant : Item
37function AccordionItem({ children, id }) {
38  const { openItem } = useAccordion();
39  const isOpen = openItem === id;
40
41  return (
42    <div style={{
43      ...styles.item,
44      borderColor: isOpen ? '#00d4ff' : 'rgba(255,255,255,0.1)',
45    }}>
46      {React.Children.map(children, child =>
47        React.cloneElement(child, { itemId: id, isOpen })
48      )}
49    </div>
50  );
51}
52
53// Sous-composant : Header
54function AccordionHeader({ children, itemId }) {
55  const { openItem, toggle } = useAccordion();
56  const isOpen = openItem === itemId;
57
58  return (
59    <button
60      onClick={() => toggle(itemId)}
61      style={styles.header}
62    >
63      <span>{children}</span>
64      <span style={{
65        transform: isOpen ? 'rotate(180deg)' : 'rotate(0)',
66        transition: 'transform 0.3s ease',
67      }}>
68        ▼
69      </span>
70    </button>
71  );
72}
73
74// Sous-composant : Panel
75function AccordionPanel({ children, isOpen }) {
76  return (
77    <div style={{
78      ...styles.panel,
79      maxHeight: isOpen ? '500px' : '0',
80      padding: isOpen ? '20px' : '0 20px',
81      opacity: isOpen ? 1 : 0,
82    }}>
83      {children}
84    </div>
85  );
86}
87
88// Nous assemblons les composants
89Accordion.Item = AccordionItem;
90Accordion.Header = AccordionHeader;
91Accordion.Panel = AccordionPanel;
92
93// ============================================
94// PATTERN : Tabs Compound Component
95// ============================================
96
97const TabsContext = createContext();
98
99function Tabs({ children, defaultTab }) {
100  const [activeTab, setActiveTab] = useState(defaultTab);
101
102  return (
103    <TabsContext.Provider value={{ activeTab, setActiveTab }}>
104      <div style={styles.tabs}>
105        {children}
106      </div>
107    </TabsContext.Provider>
108  );
109}
110
111function TabList({ children }) {
112  return (
113    <div style={styles.tabList}>
114      {children}
115    </div>
116  );
117}
118
119function Tab({ children, id }) {
120  const { activeTab, setActiveTab } = useContext(TabsContext);
121  const isActive = activeTab === id;
122
123  return (
124    <button
125      onClick={() => setActiveTab(id)}
126      style={{
127        ...styles.tab,
128        background: isActive ? '#00d4ff' : 'transparent',
129        color: isActive ? '#000' : '#fff',
130      }}
131    >
132      {children}
133    </button>
134  );
135}
136
137function TabPanels({ children }) {
138  return <div>{children}</div>;
139}
140
141function TabPanel({ children, id }) {
142  const { activeTab } = useContext(TabsContext);
143
144  if (activeTab !== id) return null;
145
146  return (
147    <div style={styles.tabPanel}>
148      {children}
149    </div>
150  );
151}
152
153Tabs.List = TabList;
154Tabs.Tab = Tab;
155Tabs.Panels = TabPanels;
156Tabs.Panel = TabPanel;
157
158// ============================================
159// App de demo
160// ============================================
161
162function CompoundComponentsDemo() {
163  return (
164    <div style={styles.container}>
165      <header style={styles.headerSection}>
166        <h1 style={styles.title}>Compound Components</h1>
167        <p style={styles.subtitle}>
168          Design pattern for components sharing state
169        </p>
170      </header>
171
172      <div style={styles.content}>
173        {/* Accordion Demo */}
174        <div style={styles.section}>
175          <h2 style={styles.sectionTitle}>Accordion</h2>
176
177          <Accordion defaultOpen="item1">
178            <Accordion.Item id="item1">
179              <Accordion.Header>What are Compound Components?</Accordion.Header>
180              <Accordion.Panel>
181                <p>
182                  Compound Components is a pattern where components share
183                  internal state through Context. This allows creating
184                  flexible and readable component APIs.
185                </p>
186              </Accordion.Panel>
187            </Accordion.Item>
188
189            <Accordion.Item id="item2">
190              <Accordion.Header>Avantages du pattern</Accordion.Header>
191              <Accordion.Panel>
192                <ul style={{ marginLeft: '20px' }}>
193                  <li>Readable and declarative syntax</li>
194                  <li>Flexibility in component layout</li>
195                  <li>Encapsulation de la logique dans le parent</li>
196                  <li>Easy addition of new features</li>
197                </ul>
198              </Accordion.Panel>
199            </Accordion.Item>
200
201            <Accordion.Item id="item3">
202              <Accordion.Header>Implémentation</Accordion.Header>
203              <Accordion.Panel>
204                <p>
205                  Implementation relies on React Context for sharing
206                  state and on static properties for grouping sub-components.
207                </p>
208              </Accordion.Panel>
209            </Accordion.Item>
210          </Accordion>
211        </div>
212
213        {/* Tabs Demo */}
214        <div style={styles.section}>
215          <h2 style={styles.sectionTitle}>Tabs</h2>
216
217          <Tabs defaultTab="tab1">
218            <Tabs.List>
219              <Tabs.Tab id="tab1">Planètes</Tabs.Tab>
220              <Tabs.Tab id="tab2">Étoiles</Tabs.Tab>
221              <Tabs.Tab id="tab3">Galaxies</Tabs.Tab>
222            </Tabs.List>
223
224            <Tabs.Panels>
225              <Tabs.Panel id="tab1">
226                <h3>Planets of the Solar System</h3>
227                <p>Mercure, Vénus, Terre, Mars, Jupiter, Saturne, Uranus, Neptune</p>
228              </Tabs.Panel>
229              <Tabs.Panel id="tab2">
230                <h3>Types d'étoiles</h3>
231                <p>Stars are classified into dwarfs, giants, supergiants and other spectral types.</p>
232              </Tabs.Panel>
233              <Tabs.Panel id="tab3">
234                <h3>Types de galaxies</h3>
235                <p>Galaxies spirales, elliptiques, irrégulières et lenticulaires.</p>
236              </Tabs.Panel>
237            </Tabs.Panels>
238          </Tabs>
239        </div>
240      </div>
241
242      <footer style={styles.footer}>
243        <p>Compound Components • Context API • Static Properties</p>
244      </footer>
245    </div>
246  );
247}
248
249const styles = {
250  container: {
251    minHeight: '100vh',
252    background: 'linear-gradient(135deg, #1a1a2e 0%, #16213e 100%)',
253    padding: '30px',
254    fontFamily: "'Segoe UI', sans-serif",
255    color: '#fff',
256  },
257  headerSection: {
258    textAlign: 'center',
259    marginBottom: '40px',
260  },
261  title: {
262    fontSize: '2.5em',
263    color: '#00d4ff',
264    marginBottom: '10px',
265  },
266  subtitle: {
267    opacity: 0.7,
268  },
269  content: {
270    maxWidth: '800px',
271    margin: '0 auto',
272  },
273  section: {
274    background: 'rgba(255,255,255,0.05)',
275    borderRadius: '15px',
276    padding: '25px',
277    marginBottom: '25px',
278    border: '1px solid rgba(255,255,255,0.1)',
279  },
280  sectionTitle: {
281    color: '#ffd700',
282    marginBottom: '20px',
283  },
284  accordion: {
285    display: 'flex',
286    flexDirection: 'column',
287    gap: '10px',
288  },
289  item: {
290    borderRadius: '10px',
291    border: '2px solid',
292    overflow: 'hidden',
293    transition: 'border-color 0.3s ease',
294  },
295  header: {
296    width: '100%',
297    padding: '15px 20px',
298    background: 'rgba(255,255,255,0.05)',
299    border: 'none',
300    color: '#fff',
301    fontSize: '16px',
302    cursor: 'pointer',
303    display: 'flex',
304    justifyContent: 'space-between',
305    alignItems: 'center',
306  },
307  panel: {
308    overflow: 'hidden',
309    transition: 'all 0.3s ease',
310    lineHeight: 1.6,
311  },
312  tabs: {},
313  tabList: {
314    display: 'flex',
315    gap: '10px',
316    marginBottom: '20px',
317  },
318  tab: {
319    padding: '12px 24px',
320    border: '2px solid #00d4ff',
321    borderRadius: '8px',
322    cursor: 'pointer',
323    fontWeight: 'bold',
324    transition: 'all 0.3s ease',
325  },
326  tabPanel: {
327    padding: '20px',
328    background: 'rgba(255,255,255,0.05)',
329    borderRadius: '10px',
330  },
331  footer: {
332    textAlign: 'center',
333    marginTop: '40px',
334    opacity: 0.7,
335  },
336};
337
338export default CompoundComponentsDemo;

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

Tâches pratiques dans le jeu

  • Clique dans l'ordre

    Ordonne la syntaxe de React.lazy avec un import dynamique :

Articles utiles