Nous avons vu qu’il est possible de décrire nos DOM virtuels React (« grappes ») avec la fonction React.createElement(…)
. Nous allons voir dans ce chapitre qu'il est préférable d'utiliser la syntaxe JSX, créée spécifiquement pour React.
Pourquoi préférer JSX ?
Lorsqu’on se limite à un petit exemple, JSX ne semble pas être d'une énorme utilité…
// Avec JSX
Oh le joli paragraphe
// sans JSX
React.createElement(p, {}, 'Oh le joli paragraphe')
Ou encore :
// Avec JSX
first="John" last="Smith"
// sans JSX
React.createElement(User, { first: 'John', last: 'Smith' })
Mais très vite, il devient difficile de se repérer. Jugez plutôt avec cette grappe pourtant assez simple :
method="post" action="/sessions" onSubmit={this.handleSubmit}
className="field"
E-mail
type="email"
name="email"
required
autoFocus
value={this.state.email}
onChange={this.handleFieldChange}
className="field"
Mot de passe
type="password"
name="password"
required
value={this.state.password}
onChange={this.handleFieldChange}
type="submit" value="Connexion"
Remarquez comme il est facile de repérer la connexion aux traitements associés (handleSubmit
, handleFieldChange
), la source des données (this.state.email
, this.state.password
), ainsi que la structure générale du composant.
Voyons maintenant l’équivalent sans JSX :
React.createElement(
'form',
{ method: 'post', action: '/sessions', onSubmit: this.handleSubmit },
React.createElement(
'p',
{ className: 'field' },
React.createElement(
'label',
null,
'E-mail',
React.createElement('input', {
type: 'email',
name: 'email',
required: true,
autoFocus: true,
value: this.state.email,
onChange: this.handleFieldChange,
})
)
),
React.createElement(
'p',
{ className: 'field' },
React.createElement(
'label',
null,
'Mot de passe',
React.createElement('input', {
type: 'password',
name: 'password',
required: true,
value: this.state.password,
onChange: this.handleFieldChange,
})
)
),
React.createElement(
'p',
null,
React.createElement('button', { type: 'submit', value: 'Connexion' })
)
)
Voyez comme ces mêmes informations sont noyées dans la masse de code, alors même que notre grappe reste modeste. Imaginez ce que ça donne pour un composant un tant soit peu complexe.
JSX, c’est bien plus que du simple confort : c’est une barrière en moins pour la compréhension rapide du fonctionnement de nos composants lorsqu’on lit le code. C’est de la maintenabilité - et de la productivité ! – en plus.
Ni du HTML, ni du XML
Dans la mesure où JSX est un langage à balises, il est tentant pour les développeurs de conserver tous leurs réflexes et modèles mentaux hérités de HTML ou d’XML. En pratique, si des similitudes existent, JSX présente aussi des différences importantes.
Parmi les points communs avec XML, on citera notamment :
La sensibilité à la casse : les majuscules et minuscules ne sont pas interchangeables,
<CoolComponent />
n’a pas le même sens que<coolComponent />
.L’exigence de fermeture des éléments :
même pour les éléments « seuls », comme par exemple
<input />
(et non pas<input>
),dans le bon ordre (on ferme dans l’ordre inverse de l'ouverture) :
<p><span>Bien</span></p>
mais pas<p><span>Pas bien</p></span>
.
En revanche, pour le reste, il va falloir ajuster ses habitudes…
Valeurs des props
En JSX, on ne parle pas d’attributs (comme en XML ou HTML), mais de props. Ce terme revient dans toute la documentation et l’écosystème React.
String vs. tout le reste
En pratique, une prop peut avoir n’importe quelle valeur possible en JavaScript, mais syntaxiquement, dans JSX, on n’a en gros que deux possibilités : un littéral String
, matérialisé par les double-quotes ""
ou une expression JSX, définie entre accolades{}
.
type="email"
name="email"
maxlength={42}
readonly={false}
onChange={this.handleFieldChange}
value={this.state.value}
Ici, les props type
et name
ont des valeurs de type String
. La syntaxe « façon HTML » s’applique donc. En XML ou en HTML il serait possible d'écrire, par exemple, maxlength=42
( ou maxlength="42"
). En JSX, il est par contre obligatoire d'utiliser une expression JSX (comme maxlength={42}
, pour notre exemple) pour toute valeur qui n'est pas de type String
.
En revanche, le type réel de l’expression JSX est préservé : la prop que l'on récupèrera dans le composant sera bien, par exemple, un nombre, un booléen, un tableau, un autre composant, etc.
Raccourci pour true
JSX garde un raccourci syntaxique classique en HTML, pour les props booléennes dont la valeur est true
. Au lieu d’écrire explicitement la valeur dans une expression JSX, on peut se contenter du nom de la prop, comme ceci :
type="email" name="email" autoFocus required
Cette syntaxe courte est d’ailleurs recommandée.
Mots réservés
Dans la mesure où JSX produit, au final, du code JavaScript, et que celui-ci doit pouvoir s’exécuter dans un environnement ES5 (par exemple Internet Explorer 9+ ou d'anciennes versions de Node.js) et non ES2015+ (navigateurs modernes, versions de Node.js récentes, etc.), il n’est pas possible d’utiliser des mots-clés de JavaScript comme noms de props.
On retrouve donc les mêmes ajustements que dans les interfaces du DOM, qui reviennent essentiellement à écrire className
au lieu de class
et htmlFor
au lieu de for
.
Commentaires
Contrairement à HTML et XML, JSX n’a pas de syntaxe dédiée pour les commentaires (par exemple <!-- … -->
) : si vous souhaitez mettre des commentaires au sein d’une grappe JSX, il faut le faire au sein d’une expression. Ça peut sembler parfois un peu chargé :
method="post" action="/sessions" onSubmit={this.handleSubmit}
{/* La classe 'field' assure l’espacement vertical convenable */}
className="field"
E-mail
type="email" name="email" required autoFocus
value={this.state.email}
{/*
Avec les champs contrôlés, il est indispensable de fournir `onChange`
pour éviter que le champ soit fourni en lecture seule au niveau du
DOM.
*/}
onChange={this.handleFieldChange}
type="submit" value="Connexion"
L’importance de la casse
On l’a dit, JSX est sensible à la casse (Majuscules / minuscules), pour ses noms d’éléments comme pour ses noms de props (par exemple, autoFocus
n’est pas la même prop que autofocus
, qui d'ailleurs n'existe pas).
Pour les noms d’éléments, c’est encore plus important :
dans une grappe JSX, si un élément démarre par une minuscule, le moteur considère qu’il s’agit d’un élément natif fourni par la plate-forme (le navigateur, par exemple),
alors que si l’élément démarre par une majuscule, on estime qu’il s’agit d’un composant React.
Le code JavaScript obtenu ne sera donc pas le même :
[
,
,
]
// donne :
[
React.createElement(CoolComponent, null),
React.createElement('coolComponent', null),
]
Notez que dans le second cas, on produit juste une String
qui donne le nom de la balise supposée native, alors que dans le premier cas, on référence bien le composant React supposé (classe ou fonction).
Mise en application
Pour pratiquer un peu JSX, nous allons ajuster notre application.
Récupérer la base de travail
Nous allons commencer par supprimer certains fichiers inutiles pour le moment dans src/
: App.test.js
, index.css
et logo.svg
. Ensuite, nous reprendrons des squelettes de travail à partir du dépôt public pour ce projet, disponible sur GitHub, afin de pouvoir nous concentrer sur la partie React.
Allez sur le dépôt GitHub, à l’étiquette debut-jsx (cliquez sur le lien)
Récupérez les feuilles de style
App.css
,Card.css
etGuessCount.css
, ainsi que les squelettes deCard.js
etGuessCount.js
. Puis posez-les dans votre dossiersrc/
.Vous pouvez alors soit apurer votre
App.js
pour être identique à celui de départ sur GitHub, soit y récupérer directementApp.js
comme base de travail.Vous pouvez aussi en profiter pour changer le
<title>
depublic/index.html
de « React App » vers le plus représentatif « React Memory ».
Écrire le composant Carte
Nous faisons un jeu de Memory. Le composant de base qui sera affiché est donc la carte. Elle devra être soit visible, soit présentée « de dos », masquée. Partons du principe que notre composant <Card />
recevra deux props : card
, qui sera le symbole à afficher ; et feedback
, qui indiquera l’état visuel de la carte : masquée ou visible. Voici comment ajuster la fonction Card
:
const Card = ({ card, feedback }) => (
className={`card ${feedback}`}
className="symbol"
{feedback === 'hidden' ? HIDDEN_SYMBOL : card}
)
Remarquez quelques points :
On déstructure directement les props passées en arguments (un gros objet de props).
Étant donné que la fonction renvoie directement une grappe de DOM virtuel, sans calcul préalable, on se dispense des accolades de bloc et du
return
, pour renvoyer directement l’expression : on trouve donc plutôt des parenthèses autour du JSX.La syntaxe de valeur textuelle pour une prop ne permet pas l’interpolation (c'est-à-dire l’incrustation de contenu dynamique dans le texte). On a donc recours pour le
<div>
principal à une expression JSX (entre accolades) qui, elle, peut utiliser la syntaxe des template strings d’ES2015, entre backquotes (`
), pour incruster dynamiquement la valeur defeedback
.
Écrire le composant Compteur de tentatives
Le composant <GuessCount />
est très simple : c’est un compteur de tentatives. Au fil de la partie, on l’affichera avant le plateau de cartes. Il attend juste une prop nommée guesses
. Voici le code final :
const GuessCount = ({ guesses }) => className="guesses"{guesses}
Écrire le composant Application
Le composant principal reste <App />
. Il est défini par une classe et non par une simple fonction, sans raison particulière pour le moment, mais ça viendra : nous en verrons les détails dans la prochaine partie de ce cours.
Pour l'instant, nous allons y poser le compteur et une série de six cartes « en dur » :
import React from 'react';
import Card from './Card'
import GuessCount from './GuessCount'
class App extends React.Component {
render() {
return (
className="memory"
guesses={0}
card="😀" feedback="hidden"
card="🎉" feedback="justMatched"
card="💖" feedback="justMismatched"
card="🎩" feedback="visible"
card="🐶" feedback="hidden"
card="🐱" feedback="justMatched"
)
}
}
Remarquez qu'il y a 4 feedbacks (ou états visuels) possibles et qui sont donc prévus :
La carte est masquée (
hidden
).La carte fait partie de la tentative en cours, qui vient de réussir une paire (
justMatched
).La carte fait partie de la tentative en cours, qui vient de rater une paire (
justMismatched
).La carte appartient à une paire précédemment réussie (
visible
).