JSX
Description
JSX est une extension de syntaxe qui permet d'écrire du code HTML directement dans du JavaScript. Bien qu'elle ne fasse partie d'aucune norme JavaScript et ne soit pas indispensable pour créer des applications, son utilisation peut être plus agréable selon vos préférences ou celles de votre équipe.
function MyComponent() {
return {
view: () => m('main', [m('h1', 'Hello world')]),
};
}
// peut être écrit comme :
function MyComponent() {
return {
view: () => (
<main>
<h1>Hello world</h1>
</main>
),
};
}
Lorsque vous utilisez JSX, vous pouvez interpoler des expressions JavaScript dans les balises JSX en utilisant des accolades :
var greeting = 'Hello';
var url = 'https://google.com';
var link = <a href={url}>{greeting}!</a>;
// donne <a href="https://google.com">Hello!</a>
Les composants peuvent être utilisés en respectant la convention de nommage qui consiste à mettre en majuscule la première lettre du nom du composant, ou en y accédant en tant que propriété :
m.render(document.body, <MyComponent />)
// équivalent à m.render(document.body, m(MyComponent))
<m.route.Link href="/home">Go home</m.route.Link>
// équivalent à m(m.route.Link, {href: "/home"}, "Go home")
Configuration
La manière la plus simple d'utiliser JSX est d'utiliser un plugin Babel.
Babel requiert npm, qui est automatiquement installé lorsque vous installez Node.js. Après avoir installé npm, créez un dossier de projet et exécutez cette commande :
npm init -y
Si vous souhaitez utiliser Webpack et Babel ensemble, consultez la section ci-dessous.
Pour installer Babel en tant qu'outil autonome, utilisez cette commande :
npm install @babel/core @babel/cli @babel/preset-env @babel/plugin-transform-react-jsx --save-dev
Créez un fichier .babelrc
:
{
"presets": ["@babel/preset-env"],
"plugins": [
[
"@babel/plugin-transform-react-jsx",
{
"pragma": "m",
"pragmaFrag": "'['"
}
]
]
}
Pour exécuter Babel, vous devez configurer un script npm. Ouvrez package.json
et ajoutez cette entrée dans la section "scripts"
:
{
"name": "my-project",
"scripts": {
"babel": "babel src --out-dir bin --source-maps"
}
}
Vous pouvez maintenant exécuter Babel en utilisant cette commande :
npm run babel
Utilisation de Babel avec Webpack
Si vous n'avez pas encore installé Webpack comme bundler, utilisez la commande suivante :
npm install webpack webpack-cli --save-dev
Vous pouvez intégrer Babel avec Webpack en suivant les étapes suivantes :
npm install @babel/core babel-loader @babel/preset-env @babel/plugin-transform-react-jsx --save-dev
Créez un fichier .babelrc
:
{
"presets": ["@babel/preset-env"],
"plugins": [
[
"@babel/plugin-transform-react-jsx",
{
"pragma": "m",
"pragmaFrag": "'['"
}
]
]
}
Ensuite, créez un fichier nommé webpack.config.js
:
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, './bin'),
filename: 'app.js',
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /\/node_modules\//,
use: {
loader: 'babel-loader',
},
},
],
},
resolve: {
extensions: ['.js', '.jsx'],
},
};
Si vous connaissez déjà Webpack, notez que l'ajout des options Babel à la section babel-loader
de votre fichier webpack.config.js
provoquera une erreur. Vous devez donc les inclure dans un fichier .babelrc
distinct.
Cette configuration suppose que le fichier de code source pour le point d'entrée de l'application se trouve dans src/index.js
, et qu'elle générera le bundle dans bin/app.js
.
Pour exécuter le bundler, configurez un script npm. Ouvrez package.json
et ajoutez cette entrée dans la section "scripts"
:
{
"name": "my-project",
"scripts": {
"start": "webpack --mode development --watch"
}
}
Vous pouvez maintenant exécuter le bundler en lançant cette commande depuis la ligne de commande :
npm start
Compilation pour la production
Pour générer un fichier minifié, ouvrez package.json
et ajoutez un nouveau script npm nommé build
:
{
"name": "my-project",
"scripts": {
"start": "webpack -d --watch",
"build": "webpack -p"
}
}
Vous pouvez utiliser des hooks dans votre environnement de production afin d'exécuter automatiquement le script de construction de production. Voici un exemple pour Heroku :
{
"name": "my-project",
"scripts": {
"start": "webpack -d --watch",
"build": "webpack -p",
"heroku-postbuild": "webpack -p"
}
}
Rendre m
accessible globalement
Pour accéder à m
globalement depuis l'ensemble de votre projet, commencez par importer webpack
dans votre fichier webpack.config.js
comme ceci :
const webpack = require('webpack');
Ensuite, créez un nouveau plugin dans la propriété plugins
de l'objet de configuration Webpack :
{
plugins: [
new webpack.ProvidePlugin({
m: 'mithril',
}),
];
}
Consultez la documentation Webpack pour plus d'informations sur ProvidePlugin
.
Différences avec React
JSX dans Mithril présente des différences subtiles mais importantes par rapport au JSX de React.
Conventions de casse pour les propriétés d'attribut et de style
React exige que vous utilisiez les noms de propriétés DOM en camelCase au lieu des noms d'attributs HTML, sauf pour les attributs data-*
et aria-*
. Par exemple, en utilisant className
au lieu de class
et htmlFor
au lieu de for
. Dans Mithril, il est plus courant d'utiliser les noms d'attributs HTML en minuscules à la place. Mithril essaie toujours de définir les attributs si une propriété n'existe pas, ce qui correspond plus intuitivement au HTML. Notez que dans la plupart des cas, les noms de propriétés DOM et les noms d'attributs HTML sont soit identiques, soit très similaires. Par exemple, value
/checked
pour les entrées et l'attribut global tabindex
par rapport à la propriété elem.tabIndex
sur les éléments HTML. Il est très rare qu'ils diffèrent au-delà de la casse : la propriété elem.className
pour l'attribut class
ou la propriété elem.htmlFor
pour l'attribut for
font partie des rares exceptions.
De même, React utilise toujours les noms de propriétés de style en camelCase exposés dans le DOM via les propriétés de elem.style
(comme cssHeight
et backgroundColor
). Mithril prend en charge à la fois cela et les noms de propriétés CSS en kebab-case (comme height
et background-color
) et préfère idiomatiquement ces derniers. Seuls cssHeight
, cssFloat
et les propriétés avec préfixes de fournisseur diffèrent de plus que la casse.
Événements DOM
React met en majuscule le premier caractère de tous les gestionnaires d'événements : onClick
écoute les événements click
et onSubmit
les événements submit
. Certains sont en outre modifiés car il s'agit de plusieurs mots concaténés ensemble. Par exemple, onMouseMove
écoute les événements mousemove
. Mithril n'effectue pas cette conversion de casse, mais ajoute simplement le préfixe on
à l'événement natif. Vous ajouteriez donc des écouteurs pour onclick
et onmousemove
pour écouter ces deux événements respectivement. Cela correspond beaucoup plus étroitement au schéma de nommage de HTML et est beaucoup plus intuitif si vous venez d'un environnement HTML ou DOM pur.
React prend en charge la planification des écouteurs d'événements pendant la phase de capture (au premier passage, de l'extérieur vers l'intérieur, par opposition à la phase de bubbling par défaut allant de l'intérieur vers l'extérieur au deuxième passage) en ajoutant Capture
à cet événement. Mithril ne dispose actuellement pas d'une telle fonctionnalité, mais il pourrait l'acquérir à l'avenir. Si cela est nécessaire, vous pouvez ajouter et supprimer manuellement vos propres écouteurs dans les hooks de cycle de vie.
JSX vs hyperscript
JSX et hyperscript sont deux syntaxes différentes que vous pouvez utiliser pour spécifier des vnodes, et elles ont des avantages et des inconvénients différents :
JSX est plus facile à adopter si vous venez d'un environnement HTML/XML et que vous êtes plus à l'aise pour spécifier des éléments DOM avec ce type de syntaxe. Il est aussi souvent plus lisible dans de nombreux cas, car il utilise moins de ponctuation et les attributs sont moins chargés visuellement, de sorte que de nombreuses personnes le trouvent beaucoup plus facile à lire. Et bien sûr, de nombreux éditeurs courants offrent une prise en charge de l'autocomplétion pour les éléments DOM de la même manière qu'ils le font pour HTML. Cependant, il nécessite une étape de build supplémentaire pour être utilisé, la prise en charge par les éditeurs n'est pas aussi étendue qu'avec du JS standard, et il est considérablement plus verbeux. Il est également un peu plus verbeux lorsqu'il s'agit de beaucoup de contenu dynamique, car vous devez utiliser des interpolations pour tout.
Hyperscript est plus facile à adopter si vous venez d'un environnement JS backend qui n'implique pas beaucoup de HTML ou de XML. Il est plus concis avec moins de redondance, et il fournit un sucre de syntaxe de type CSS pour les classes statiques, les ID et autres attributs. Il peut également être utilisé sans aucune étape de build, bien que vous puissiez en ajouter une si vous le souhaitez. Et il est légèrement plus facile de travailler avec lui face à beaucoup de contenu dynamique, car vous n'avez pas besoin d'"interpoler" quoi que ce soit. Cependant, cette concision le rend plus difficile à lire pour certaines personnes, en particulier celles qui sont moins expérimentées et qui viennent d'un arrière-plan HTML/CSS/XML. Je ne connais aucun plugin qui auto-complète des parties de sélecteurs hyperscript comme les ID, les classes et les attributs.
Vous pouvez observer les avantages et les inconvénients dans des arborescences plus complexes. Par exemple, considérez cette arborescence hyperscript, adaptée d'un projet réel par @dead-claudia avec quelques modifications pour plus de clarté et de lisibilité :
function SummaryView() {
let tag, posts;
function init({ attrs }) {
Model.sendView(attrs.tag != null);
if (attrs.tag != null) {
tag = attrs.tag.toLowerCase();
posts = Model.getTag(tag);
} else {
tag = undefined;
posts = Model.posts;
}
}
function feed(type, href) {
return m('.feed', [
type,
m('a', { href }, m('img.feed-icon[src=./feed-icon-16.gif]')),
]);
}
return {
oninit: init,
// To ensure the tag gets properly diffed on route change.
onbeforeupdate: init,
view: () =>
m('.blog-summary', [
m('p', 'My ramblings about everything'),
m('.feeds', [
feed('Atom', 'blog.atom.xml'),
feed('RSS', 'blog.rss.xml'),
]),
tag != null
? m(TagHeader, { len: posts.length, tag })
: m('.summary-header', [
m('.summary-title', 'Posts, sorted by most recent.'),
m(TagSearch),
]),
m(
'.blog-list',
posts.map(post =>
m(
m.route.Link,
{
class: 'blog-entry',
href: `/posts/${post.url}`,
},
[
m(
'.post-date',
post.date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
})
),
m('.post-stub', [
m('.post-title', post.title),
m('.post-preview', post.preview, '...'),
]),
m(TagList, { post, tag }),
]
)
)
),
]),
};
}
Voici l'équivalent exact du code ci-dessus, en utilisant JSX à la place. Vous pouvez voir comment les deux syntaxes diffèrent juste dans ce bit, et quels avantages et inconvénients s'appliquent.
function SummaryView() {
let tag, posts;
function init({ attrs }) {
Model.sendView(attrs.tag != null);
if (attrs.tag != null) {
tag = attrs.tag.toLowerCase();
posts = Model.getTag(tag);
} else {
tag = undefined;
posts = Model.posts;
}
}
function feed(type, href) {
return (
<div class="feed">
{type}
<a href={href}>
<img class="feed-icon" src="./feed-icon-16.gif" />
</a>
</div>
);
}
return {
oninit: init,
// To ensure the tag gets properly diffed on route change.
onbeforeupdate: init,
view: () => (
<div class="blog-summary">
<p>My ramblings about everything</p>
<div class="feeds">
{feed('Atom', 'blog.atom.xml')}
{feed('RSS', 'blog.rss.xml')}
</div>
{tag != null ? (
<TagHeader len={posts.length} tag={tag} />
) : (
<div class="summary-header">
<div class="summary-title">Posts, sorted by most recent</div>
<TagSearch />
</div>
)}
<div class="blog-list">
{posts.map(post => (
<m.route.Link class="blog-entry" href={`/posts/${post.url}`}>
<div class="post-date">
{post.date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
})}
</div>
<div class="post-stub">
<div class="post-title">{post.title}</div>
<div class="post-preview">{post.preview}...</div>
</div>
<TagList post={post} tag={tag} />
</m.route.Link>
))}
</div>
</div>
),
};
}
Conseils et astuces
Conversion de HTML en JSX
Dans Mithril.js, le HTML bien formé est généralement un JSX valide. Il suffit généralement d'utiliser du HTML brut pour que les choses fonctionnent. Les seules choses que vous auriez normalement à faire sont de changer les valeurs de propriétés non guillemetées comme attr=value
en attr="value"
et de changer les éléments vides comme <input>
en <input />
, ceci étant dû au fait que JSX est basé sur XML et non sur HTML.
Pour accélérer ce processus, vous pouvez utiliser un convertisseur HTML vers modèle Mithril créé par la communauté pour faire une grande partie du travail pour vous.