React.js
React est une bibliothèque JavaScript développée par Facebook depuis 2013
Elle permet de :
Comparaison
import { Component, Input } from '@angular/core';
@Component({
selector: 'hello',
template: `<h1>Hello {{name}} !</h1>`,
styles: [`h1 { font-family: Lato; }`]
})
export class HelloComponent {
@Input() name: string;
}
<template>
<h1>Hello {{name}} !</h1>
</template>
<script>
export default {
name : 'HelloComponent',
props : {
name : String
}
}
</script>
<style scoped>
</style>
import React from 'react';
import PropTypes from 'prop-types';
export default function HelloWorld(props) {
return (
<h1>
Hello {props.name} !
</h1>
);
}
HelloWorld.propTypes = {
name: PropTypes.string
};
Angular et Vue sont des frameworks
React est une librairie UI nécessitant d'autres librairies pour devenir un framework
Angular est le plus complexe, suivi par React et enfin Vue
Bien maîtrisés, les trois solutions permettent d'obtenir des performances quasi-identiques
Il faut choisir la solution en fonction du projet
Google Trends
NPM Trends
JSX est un langage développé par Facebook et qui permet de générer des éléments React avec une syntaxe rappelant HTML
Le développeur React écrit en JSX, lequel est compilé en JavaScript
La compilation se fait avec un transpiler,
comme
Babel
Par exemple, on écrit ceci :
const siteName = 'My portfolio';
const navbar = (
<nav>
<a href="/home">{siteName}</a>
<ul>
<li>Home</li>
<li>Projects</li>
<li>About</li>
</ul>
</nav>
);
… qui sera compilé par Babel en ceci :
const siteName = "My portfolio";
const navbar = React.createElement(
"nav", null,
React.createElement( "a", { href: "/home" }, siteName ),
React.createElement(
"ul", null,
React.createElement("li", null, "Home"),
React.createElement("li", null, "Projects"),
React.createElement("li", null, "About")
)
);
Le JSX permet au développeur de facilement écrire l'interface avec un langage proche du HTML
Nous verrons plus en détail les particularités de ce langage plus loin dans le cours …
Démarrer un projet React en écrivant à la main toute la configuration nécessaire à la compilation est fastidieux
Au début, on utilisait un package NPM appelé
create-react-app
Ce programme est toutefois assez lent à l'exécution.
Depuis quelques temps maintenant, c'est le package ViteJS qui est conseillé
vite
Ce programme va générer un projet React
pré-configuré et prêt-à-l'emploi
Créer un projet React du nom de "myapp" :
npm create vite@latest myapp -- --template react
Cela peut prendre un peu de temps …
cd myapp
npm install
npm run dev
Cela va lancer un serveur de développement, accessible dans le navigateur à l'adresse http://localhost:5173
Le projet généré aura la structure suivante :
On travaillera majoritairement dans les dossiers
public/ et src/
Le fichier principal src/main.jsx correspond
au point de démarrage
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
Lors de la compilation, c'est ce fichier qui sera lu en premier
Étudions ce fichier par petits morçeaux …
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'
App.jsx
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
public/index.html
Le composant <App/> est le composant principal qui sera rendu dans la page web par React
Un composant React est une fonction rangée dans
un fichier .jsx et qui porte généralement le même nom
Ce fichier doit exporter par défaut la fonction définissant le composant
src/App.jsx
import { useState } from 'react'
import reactLogo from './assets/react.svg'
import './App.css'
function App() {
const [count, setCount] = useState(0)
return (
<div className="App">
<div>
<a href="https://vitejs.dev" target="_blank">
<img src="/vite.svg" className="logo" alt="Vite logo" />
</a>
<a href="https://reactjs.org" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
<p>
Edit <code>src/App.jsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">
Click on the Vite and React logos to learn more
</p>
</div>
)
}
export default App
Remplacez le contenu de App.jsx par ceci :
import React from 'react';
import './App.css';
function App() {
const monTitre = 'Hello React!';
return (
<div className="App">
<h1>{monTitre}</h1>
</div>
);
}
export default App;
Supprimez également les fichiers assets/react.svg, public/vite.svget les styles présents dans App.css
L'affichage de variables en JSX se fait
entre accolades { }
const monTitre = 'Hello React!';
return (
<h1>{monTitre}</h1>
);
On appelle cela une expression
On peut écrire du "Vanilla JavaScript" dans les expressions :
const monTitre = 'Hello React!';
return (
<h1>{monTitre.toUpperCase()}</h1>
);
Il s'agit bien de JS standard. On peut utiliser tout ce qu'on connaît du langage 👍
Les expressions peuvent être utilisées
partout dans le JSX
const cardStyle = { flex : 'initial' };
const photoLink = './path/to/image.jpg';
const title = 'One Life';
const artist = 'Ed Sheeran';
const album = 'Yesterday OST';
const musicFile = './path/to/music.mp3';
return (
<div className="card w-50" style={cardStyle}>
<img className="align-self-center mr-2 w-25" src={photoLink} alt=""/>
<h5 className="card-title">{title}</h5>
<h6 className="card-subtitle mb-2 text-muted">{artist} / {album}</h6>
<audio src={musicFile} className="w-100" controls></audio><br/>
</div>
);
Les éléments doivent tous êtres refermés
par un slash /
(<div>
<br/>
<img src="…" alt="" />
<hr/>
…
</div>)
On applique la syntaxe du langage XML et non du HTML 5
Certains attributs HTML comme class ou
for doivent être remplacés par des équivalents
(<label htmlFor="searchText" className="col-form-label">
Trier par :
</label>)
Cela est dû au fait que "class" et "for" sont des mots-clés réservés en JavaScript
Un composant ne doit retourner qu'un seul noeud racine
function App() {
const monTitre = 'Hello React!';
return (
<div>
<h1>{monTitre}</h1>
<p>Bienvenue sur ma page web faite avec React</p>
</div>
);
}
Si on ne souhaite pas générer de <div> superflue, on peut utiliser les React Fragments
function App() {
const monTitre = 'Hello React!';
return (
<>
<h1>{monTitre}</h1>
<p>Bienvenue sur ma page web faite avec React</p>
</>
);
}
« React : Bien démarrer »
https://fr.reactjs.org/docs/getting-started.html
React.js
Le principe des composants est fondé sur la notion d’autonomie et de réutilisabilité. Ils permettent ainsi de considérer chaque élément de manière isolée.
Les éléments HTML sont de parfaits exemples de composants.
<input type="text" maxlength="10" placeholder="Votre texte ici…" />
<video width="400" height="225" controls>
<source src="bigbuckbunny.webm" type="video/webm">
</video>
Un composant est donc un élément qui embarque :
Tout comme les balises HTML standards, les composants permettent de mieux structurer les applications web en les découpant en plusieurs morceaux ré-assemblables, autonomes et fonctionnels.
Au sens de React, les composants sont comme des fonctions
JavaScript.
Ils acceptent des entrées (appelées « props ») et renvoient des
éléments décrivant ce qui doit s'afficher à l'écran.
Le moyen le plus simple de définir un composant React consiste à écrire une fonction JavaScript :
function Jedi (props) {
const title = props.isMaster ? 'Maître' : 'jeune Padawan';
return <h1>Bienvenue {title} {props.name}</h1>
}
Cette fonction est un composant React car elle accepte un seul argument "props" qui est un objet contenant des données, et renvoie un élément JSX à afficher.
Cela s'appelle une fonction composant
Il est également possible d'utiliser les classes ES2015 pour définir un composant :
class Welcome extends React.Component {
render() {
const title = this.props.isMaster ? 'Maître' : 'jeune Padawan';
return <h1>Bienvenue {title} {this.props.name}</h1>
}
}
Les deux façons de faire sont équivalentes et disposent de fonctionnalités supplémentaires afin de les rendre intéractifs.
Nous étudierons ici les function components, plus réçents et plus succints à écrire.
Un composant s'écrit à la manière d'une balise HTML :
// Jedi.jsx
export default function Jedi (props) {
const title = props.isMaster ? 'Maître' : 'jeune Padawan';
return <h1>Bienvenue {title} {props.name}</h1>
}
// App.jsx
import Jedi from './Jedi.jsx';
export default function App() {
return (
<main>
<Jedi name="Obi-Wan Kenobi" isMaster={true} />
</main>
)
}
⚠ React considère les composants commençant par des minuscules comme des balises DOM.
Par exemple, <div> représente une balise HTML,
mais <Welcome /> représente un composant et
exige qu'il soit référencé dans la portée courante
(= importé).
Les composants permettent de faire de la composition et d'être réutilisés autant de fois que souhaité :
<main class="app">
<Jedi name="Obi-Wan Kenobi" isMaster={true} />
<Jedi name="Qui-Gon Jinn" isMaster={true} />
<Jedi name="Anakin Skywalker" isMaster={false} />
</main>
Les props sont aux composants ce que les attributs sont aux balises HTML.
<input type="checkbox" checked="checked">
« type » et « checked » sont des attributs de
la balise <input>
<Jedi name="Obi-Wan Kenobi" isMaster={true} />
« name » et « isMaster » sont des props du
composant <Jedi>
Dans la définition du composant, elles se récupèrent dans le seul paramètre « props » de la fonction
(ou dans this.props pour les
class components)
function Jedi (props) {
const name = props.name;
const isMaster = props.isMaster;
…
}
À noter qu'on peut utiliser la syntaxe de décomposition ES2015 sur l'objet « props »
function Jedi ({name, isMaster}) {
…
}
Les props sont toujours en lecture seule ! Il n'est pas possible de les modifier.
function Jedi (props) {
props.name = props.name.toUpperCase(); ❌
…
}
Cela part du principe simple que les fonctions React sont tout le temps des fonctions pures
Pour faire évoluer l'état d'un composant, on ne peut donc pas faire muter les props.
On utilisera alors plutôt le concept d'état local, qui sera présenté dans le prochain cours…
« React : Components et props »
https://fr.reactjs.org/docs/components-and-props.html
React.js
La gestion des événements en React se fait de la même façon qu'en HTML classique, malgré quelques différences de syntaxe (et de fonctionnement interne)
En HTML classique, on peut écrire :
<button onclick="startApp()">
Démarrer l'application
</button>
En React, on écrit plutôt :
<button onClick={startApp}>
Démarrer l'application
</button>
Généralement, il n'est pas conseillé d'employer la méthode du HTML classique pour des raisons de séparation entre la structure et la présentation du code.
De plus, référencer un gestionnaire d'événement via un attribut revient à rendre disponible la fonction JS dans l'espace global, et/ou potentiellement limiter les performances si le HTML venait à changer dynamiquement.
❌
… en gros, ne faites jamais ceci en HTML classique :
<button onclick="startApp()">
Démarrer l'application
</button>
Préférez plutôt l'utilisation de
addEventListener en JS pur.
En React cependant, même si la syntaxe recommandée ressemble à celle déconseillée en HTML, le fonctionnement interne n'est pas du tout le même.
React gère intelligemment et de façon optimisée les gestionnaires d'événements, leur propagation et leur remontée (bubbling).
Seul le côté pratique de la syntaxe a été conservé.
✔
En React, on écrit donc ceci :
<button onClick={startApp}>
Démarrer l'application
</button>
Remarquez que le nom de l'événement s'écrit en
camelCase, et que la valeur de la prop
référence une fonction, sans l'exécuter avec les parenthèses
()
Les fonctions référencées se comportent comme des fonctions d'événement JS classique, aux différences suivantes :
event est un
événement
synthétique
créé par React.
false pour
stopper le comportement par défaut de la fonction. On utilise
plutôt event.preventDefault() pour cela.
this pour désigner
l'élément ayant déclanché l'événement. On utilise plutôt
event.target pour cela.
export default function App () {
function onFruitClick(event) {
event.preventDefault();
const fruit = event.target.textContent;
console.log(`Vous avez cliqué sur ${fruit}`);
}
return (
<ul>
<li onClick={onFruitClick}>Pêche</li>
<li onClick={onFruitClick}>Banane</li>
<li onClick={onFruitClick}>Leetchi</li>
</ul>
);
}
Tester ce code
Tous les événements JavaScript peuvent s'écouter avec cette syntaxe :
<form onSubmit={ … }>
<div onMouseMove={ … }>
<input onKeyDown={ … }>
<select onBlur={ … } onFocus={ … }>
<video onPlay={ … }>
On a parfois besoin de vouloir passer un argument supplémentaire à un gestionnaire d'événement.
Dans ce cas, on utilise une fonction fléchée :
<button onClick={(event) => deleteTasks(task, event)}>
Supprimer la tâche
</button>
« React : Gérer les événements »
https://fr.reactjs.org/docs/handling-events.html
React.js
Les hooks sont arrivés dans la
version 16.8.0 de React
Ils permettent d'attacher un comportement réutilisable à un composant de type function
Avant cela, on utilisait des solutions telles que les props de rendu ou les composants d'ordre supérieur
Mais cette approche nécessitait la restructuration des composants, et rendait le code plus lourd et difficile à maintenir
React a besoin d’une meilleure primitive pour la réutilisation des logiques à état.
Les Hooks vous permettent de réutiliser de la logique à état sans modifier la hiérarchie de vos composants.
Concrètement, ce sont des fonctions qui permettent de « se brancher » sur la gestion d’état local et le cycle de vie de React
Ils s'appellent directement à la racine du composant, et ne fonctionnent que pour les composants de type function
React offre plusieurs Hooks de base :
useState()
useRef()
useEffect()
useContext()
useState()
useState permet de déclarer des variables d'état
dynamiques
L'avantage d'une telle variable est que React peut surveiller son changement de valeur, et mettre à jour la vue en conséquence
Pour utiliser ce hook, il faut d'abord l'importer :
import React, {useState} from 'react';
Puis on l'utilise dans la fonction de composant :
const [prenom, setPrenom] = useState('');
useState accepte un argument définissant la valeur
par défaut de la variable
Elle renvoie un tableau composé de 2 valeurs :
la variable elle-même et une
fonction permettant de la mettre à jour
Par convention, la méthode de modification commence par « set »
Exemple complet :
import React, {useState} from 'react';
export default function Hello() {
const [prenom, setPrenom] = useState('');
function onTextChange(event) {
setPrenom(event.target.value);
}
return (
<div>
<h1>Hello {prenom} !</h1>
<input type="text" onChange={onTextChange} />
</div>
);
}
⚠ La méthode de modification doit systématiquement définir un nouvel objet
Il faut donc faire attention lorsqu'on modifie une variable d'état
contenant un objet littéral {} ou un
tableau [] JavaScript à
ne pas faire muter l'objet, mais bien le remplacer
!
❌ Ne pas faire :
// Ajout d'un nouvel article
articles.push("Fraises");
setArticle(articles);
✅ Faire :
// Ajout d'un nouvel article
setArticle([...articles, "Fraises"]);
❌ Ne pas faire :
// Masque un élément
style.display = 'none';
setStyle(style);
✅ Faire :
// Masque un élément
setStyle({
...style,
display: 'none'
});
useRef()
Le hook de référence renvoie un objet « ref » modifiable dont la propriété current est initialisée avec
l'argument fourni.
import React, { useRef } from 'react';
export default function Hello() {
const myref = useRef(0);
return <>{myref.current}</>;
}
On s'en sert quand on veut conserver une valeur entre différents re-renders.
import React, { useRef } from 'react';
export default function Hello() {
const rendersCounter = useRef(0);
rendersCounter.current++;
return <p>Composant rendu {rendersCounter.current} fois!</p>;
}
On peut aussi l'utiliser pour accéder directement à un élément du DOM grâce à la prop ref={}
import React, { useRef } from 'react';
export default function Hello() {
const inputEl = useRef(null);
function onButtonClick() {
// `current` fait référence au champ <input> ci-dessous
inputEl.current.focus();
};
return (
<>
<input type="text" ref={inputEl} />
<button onClick={onButtonClick}>Donner le focus</button>
</>
);
}
En gros, useRef est comme une « boîte » qui
pourrait contenir une valeur modifiable dans sa propriété
.current.
useEffect()
Le hook d'effet permet l'exécution d'effets de bord dans les fonctions composants
Concrètement, il permet de réagir à des actions comme :
Il est très pratique lorsqu'on a besoin de faire un appel vers une API quand le composant démarre, ou pour déclencher des actions suite à une modification du state
Pour utiliser ce Hook, on l'importe depuis React :
import React, { useEffect } from 'react';
Puis on le renseigne dans le composant de fonction:
function Hello() {
useEffect(() => {
// Le code d'effet ici …
});
}
Exemple complet :
import React, {useState, useEffect} from 'react';
export default function App () {
const [counter, setCounter] = useState(0);
useEffect(() => {
// Met à jour le titre du document
document.title = `Vous avez cliqué ${counter} fois !`;
});
function increment() {
setCounter(counter + 1);
}
return (
<button onClick={increment}>{counter}</button>
);
}
Par défaut, useEffect va s'exécuter
à chaque fois que le composant est mis à jour, ce
qui n'est pas toujours le comportement souhaité
Pour cela, on peut préciser comme 2ème paramètre un
Array [] vide :
function Hello() {
useEffect(() => {
// Le code d'effet ici …
}, []);
}
Ce code d'effet ne s'exécutera qu'une seule fois au démarrage
du composant
(ce qui est pratique pour initialiser un appel API par
exemple)
⚠️ Attention !
Depuis React 18, le hook d'effet au démarrage va s'exécuter 2 fois (en mode Strict).
Plus d'infos ici : reactjs.org/docs/strict-mode.html#ensuring-reusable-state
Si on doit faire un API call au chargement d'un composant, il faut alors s'assurer de ne le faire qu'une fois :
import React, { useEffect, useRef } from 'react';
function UserPage({ userId }) {
const effectCalled = useRef(false);
useEffect(() => {
async function fetchUser() {
fetch(`/api/users/${userId}`).then(…);
}
if (!effectCalled.current) {
fetchUser();
effectCalled.current = true;
}
}, []);
}
On peut aussi renseigner une ou plusieurs variables d'état dans l'Array, pour n'effectuer une action que si ces variables là sont modifiées :
function Hello() {
useEffect(() => {
// Le code d'effet ici …
}, [counter, prenom]);
}
Ce code d'effet ne s'exécutera que lorsque les variables
counter ou prenom seront
modifiées
Le hook d'effet peut parfois avoir besoin d'être « nettoyé » lors de la suppression du composant
Par exemple, si au démarrage du composant, on écrit un
gestionnaire d'événement avec addEventListener, il
faut également écrire le
removeEventListener correspondant, et ce pour des
raisons de performance
Ainsi, chaque Hook d'effet peut renvoyer une fonction qui sera appelée par React dès qu'un nettoyage s'impose :
function Hello() {
useEffect(() => {
// Attache un gestionnaire d'événement
document.addEventListener('mousemove', onMouseMoving);
return () => {
// Détache le gestionnaire d'événement créé au démarrage
document.removeEventListener('mousemove', onMouseMoving);
};
}, []);
}
https://fr.reactjs.org/docs/hooks-effect.html#effects-with-cleanup
useContext()
Le hook de contexte résoud une problématique simple, à savoir la transmission de valeur d'un composant à l'autre
D'ordinaire, lorsqu'on souhaite transmettre une valeur d'un composant parent à un composant enfant, on utilise les props :
const todos = [ {title:"Acheter du pain"} , {title:"Nourrir le chat"} ];
function App() {
return (
<Todolist todos={todos} />
);
}
function Todolist(props) {
return (
<ul>
{props.todos.map((todo, index) => (
<li key={index}>{todo.title}</li>
))}
</ul>
);
}
Transmettre des valeurs via les props est une bonne méthode quand les composants sont proches …
… mais parfois, les composants sont éloignés …
Le passage via les props va déboucher sur
l’anti pattern de « forage des props »
( props drilling )
Pour éviter cela, nous avons à notre disposition
le Hook Context
Le Contexte permet de mettre à disposition des données globales via un Provider.
Tout composant ayant besoins de ces données pourra y accéder via un Consumer.
On utilisera le Contexte uniquement quand on ne peut faire autrement, car il rend la réutilisation des composants plus difficile.
Pour créer un Contexte, il faut définir un module qui met à disposition les données via un Provider et un Consumer
On initialise un nouveau contexte avec la méthode
createContext() de React
UserContext.js
import React, {createContext} from 'react';
const UserContext = createContext({ name: "Zuckerberg", age: 35 });
export const UserProvider = UserContext.Provider;
export const UserConsumer = UserContext.Consumer;
export default UserContext;
Ensuite, pour que le contexte soit accessible, il doit faire parti des composants parents de ceux qui vont y accéder
Le Provider met à disposition les données
App.js
import React from 'react';
import Hello from './Hello';
import { UserProvider } from './UserContext';
function App() {
const user = { name: 'Wozniak', age: 69 };
return (
<UserProvider value={user}>
<Hello />
</UserProvider>
)
}
La prop "value" du Provider va mettre à disposition l'objet
user
à tous les enfants de ce Provider
Enfin, lorsqu'on souhaite dans un composant enfant accéder au
contexte, on utilisera le hook useContext()
Hello.js
import React, {useContext} from 'react';
import UserContext from './UserContext';
function HomePage() {
const user = useContext(UserContext);
return (
<h3>{user.name}</h3> // ✅
<span>{user.age}</span> // ✅
);
}
On créé le contexte avec React.createContext()
On exporte le Provider et le Consumer du contexte
On enveloppe les composants avec le Provider
On utilise le hook useContext() pour accéder aux
données
Hooks complémentaires de React :
useReduceruseCallbackuseMemouseImperativeHandleuseLayoutEffectuseDebugValuehttps://fr.reactjs.org/docs/hooks-reference.html#additional-hooks
« React : Introduction aux hooks »
https://fr.reactjs.org/docs/hooks-intro.html