JSX
Descrizione
JSX è un'estensione della sintassi che permette di scrivere tag HTML all'interno del codice JavaScript. Non è parte di alcuno standard JavaScript e non è strettamente necessario per creare applicazioni, ma può risultare più intuitivo a seconda delle preferenze personali o del team.
function MyComponent() {
return {
view: () => m('main', [m('h1', 'Hello world')]),
};
}
// può essere scritto come:
function MyComponent() {
return {
view: () => (
<main>
<h1>Hello world</h1>
</main>
),
};
}
Quando si usa JSX, è possibile inserire espressioni JavaScript all'interno dei tag JSX utilizzando le parentesi graffe:
var greeting = 'Hello';
var url = 'https://google.com';
var link = <a href={url}>{greeting}!</a>;
// produce <a href="https://google.com">Hello!</a>
I componenti possono essere utilizzati capitalizzando la prima lettera del nome del componente o accedendovi come proprietà:
m.render(document.body, <MyComponent />)
// equivalente a m.render(document.body, m(MyComponent))
<m.route.Link href="/home">Go home</m.route.Link>
// equivalente a m(m.route.Link, {href: "/home"}, "Go home")
Configurazione
Il modo più semplice per utilizzare JSX è tramite un plugin di Babel.
Babel richiede npm, che viene installato automaticamente quando si installa Node.js. Una volta installato npm, crea una directory di progetto ed esegui questo comando:
npm init -y
Se si desidera utilizzare Webpack e Babel insieme, passare alla sezione successiva.
Per installare Babel come strumento autonomo, usa questo comando:
npm install @babel/core @babel/cli @babel/preset-env @babel/plugin-transform-react-jsx --save-dev
Crea un file .babelrc
:
{
"presets": ["@babel/preset-env"],
"plugins": [
[
"@babel/plugin-transform-react-jsx",
{
"pragma": "m",
"pragmaFrag": "'['"
}
]
]
}
Per eseguire Babel, configura uno script npm. Apri package.json
e aggiungi questa voce sotto "scripts"
:
{
"name": "my-project",
"scripts": {
"babel": "babel src --out-dir bin --source-maps"
}
}
Ora puoi eseguire Babel usando questo comando:
npm run babel
Usare Babel con Webpack
Se non hai già installato Webpack come bundler, usa questo comando:
npm install webpack webpack-cli --save-dev
Puoi integrare Babel in Webpack seguendo questi passaggi.
npm install @babel/core babel-loader @babel/preset-env @babel/plugin-transform-react-jsx --save-dev
Crea un file .babelrc
:
{
"presets": ["@babel/preset-env"],
"plugins": [
[
"@babel/plugin-transform-react-jsx",
{
"pragma": "m",
"pragmaFrag": "'['"
}
]
]
}
Successivamente, crea un file chiamato 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'],
},
};
Per chi ha già familiarità con Webpack, si noti che aggiungere le opzioni Babel direttamente alla sezione babel-loader
del file webpack.config.js
causerà un errore. È quindi necessario includerle nel file .babelrc
separato.
Questa configurazione presuppone che il file di codice sorgente per il punto di ingresso dell'applicazione si trovi in src/index.js
e che il bundle verrà generato in bin/app.js
.
Per eseguire il bundler, configura uno script npm. Apri package.json
e aggiungi questa voce sotto "scripts"
:
{
"name": "my-project",
"scripts": {
"start": "webpack --mode development --watch"
}
}
Ora puoi eseguire il bundler eseguendo questo comando dalla riga di comando:
npm start
Build di produzione
Per generare un file minimizzato, apri package.json
e aggiungi un nuovo script npm chiamato build
:
{
"name": "my-project",
"scripts": {
"start": "webpack -d --watch",
"build": "webpack -p"
}
}
Puoi utilizzare gli hook nel tuo ambiente di produzione per eseguire automaticamente lo script di build di produzione. Ecco un esempio per Heroku:
{
"name": "my-project",
"scripts": {
"start": "webpack -d --watch",
"build": "webpack -p",
"heroku-postbuild": "webpack -p"
}
}
Rendere m
accessibile globalmente
Per poter accedere a m
globalmente da tutto il tuo progetto, prima importa webpack
nel tuo webpack.config.js
in questo modo:
const webpack = require('webpack');
Quindi crea un nuovo plugin nella proprietà plugins
dell'oggetto di configurazione Webpack:
{
plugins: [
new webpack.ProvidePlugin({
m: 'mithril',
}),
];
}
Vedi la documentazione di Webpack per maggiori informazioni su ProvidePlugin
.
Differenze con React
JSX in Mithril ha alcune differenze sottili ma importanti rispetto al JSX di React.
Convenzioni di case delle proprietà di attributi e stili
React richiede di usare i nomi delle proprietà DOM in camelCase invece dei nomi degli attributi HTML per tutti gli attributi diversi dagli attributi data-*
e aria-*
. Ad esempio, usare className
invece di class
e htmlFor
invece di for
. In Mithril, è più comune utilizzare i nomi degli attributi HTML in minuscolo. Mithril utilizza sempre l'impostazione degli attributi se una proprietà non esiste, il che si allinea in modo più intuitivo con HTML. Si noti che nella maggior parte dei casi, i nomi delle proprietà DOM e degli attributi HTML sono uguali o molto simili. Ad esempio, value
/checked
per gli input e l'attributo globale tabindex
rispetto alla proprietà elem.tabIndex
sugli elementi HTML. Molto raramente differiscono oltre l'uso di maiuscole/minuscole: la proprietà elem.className
per l'attributo class
o la proprietà elem.htmlFor
per l'attributo for
sono tra le poche eccezioni.
Allo stesso modo, React usa sempre i nomi delle proprietà di stile in camelCase esposti nel DOM tramite le proprietà di elem.style
(come cssHeight
e backgroundColor
). Mithril supporta sia questo che i nomi delle proprietà CSS in kebab-case (come height
e background-color
) e preferisce idiomaticamente quest'ultimo. Solo cssHeight
, cssFloat
e le proprietà con prefisso del vendor differiscono in qualcosa di più del case.
Eventi DOM
React mette in maiuscolo il primo carattere di tutti i gestori di eventi: onClick
ascolta gli eventi click
e onSubmit
per gli eventi submit
. Alcuni vengono ulteriormente modificati poiché sono più parole concatenate insieme. Ad esempio, onMouseMove
ascolta gli eventi mousemove
. Mithril non esegue questa mappatura di case, ma aggiunge semplicemente on
all'evento nativo, quindi dovresti aggiungere listener per onclick
e onmousemove
per ascoltare rispettivamente questi due eventi. Ciò corrisponde molto più da vicino allo schema di denominazione di HTML ed è molto più intuitivo se si proviene da un background HTML o DOM vanilla.
React supporta la pianificazione dei listener di eventi durante la fase di acquisizione (nel primo passaggio, da fuori a dentro, al contrario della fase di bubble predefinita che va da dentro a fuori nel secondo passaggio) aggiungendo Capture
a quell'evento. Mithril attualmente non ha tale funzionalità, ma potrebbe acquisirla in futuro. Se ciò è necessario, puoi aggiungere e rimuovere manualmente i tuoi listener negli hook del ciclo di vita.
JSX vs hyperscript
JSX e hyperscript sono due sintassi diverse che puoi usare per specificare i vnode e hanno diversi compromessi:
JSX è molto più accessibile se provieni da un background HTML/XML e ti senti più a tuo agio a specificare elementi DOM con quel tipo di sintassi. È anche leggermente più pulito in molti casi poiché utilizza meno punteggiatura e gli attributi contengono meno rumore visivo, quindi molte persone lo trovano molto più facile da leggere. E, naturalmente, molti editor comuni forniscono il supporto per il completamento automatico per gli elementi DOM nello stesso modo in cui lo fanno per HTML. Tuttavia, richiede un passaggio di build aggiuntivo per essere usato, il supporto dell'editor non è così ampio come lo è con il normale JS ed è considerevolmente più prolisso. È anche un po' più prolisso quando si ha a che fare con molti contenuti dinamici perché devi usare le interpolazioni per tutto.
Hyperscript è più accessibile se provieni da un background JS backend che non coinvolge molto HTML o XML. È più conciso con meno ridondanza e fornisce uno zucchero sintattico simile a CSS per classi statiche, ID e altri attributi. Può anche essere usato senza alcun passaggio di build, anche se puoi aggiungerne uno se lo desideri. Ed è leggermente più facile da usare di fronte a molti contenuti dinamici, perché non è necessario "interpolare" nulla. Tuttavia, la concisione lo rende più difficile da leggere per alcune persone, specialmente quelle meno esperte e provenienti da uno sfondo HTML/CSS/XML front-end, e non sono a conoscenza di alcun plugin che completi automaticamente parti di selettori hyperscript come ID, classi e attributi.
Puoi vedere i compromessi manifestarsi in alberi più complessi. Ad esempio, si consideri questo esempio hyperscript, adattato da un progetto reale di @dead-claudia con alcune modifiche per chiarezza e leggibilità:
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,
// Per garantire che il tag venga differenziato correttamente al cambio di rotta.
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 }),
]
)
)
),
]),
};
}
Ecco l'esatto equivalente del codice sopra, usando invece JSX. Puoi vedere come le due sintassi differiscono proprio in questo bit e quali compromessi si applicano.
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,
// Per garantire che il tag venga differenziato correttamente al cambio di rotta.
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>
),
};
}
Suggerimenti e trucchi
Conversione di HTML in JSX
In Mithril.js, l'HTML ben formato è generalmente JSX valido. Per far funzionare il tutto, è sufficiente incollare codice HTML grezzo. Le uniche cose che normalmente dovresti fare sono cambiare i valori delle proprietà senza virgolette come attr=value
in attr="value"
e cambiare gli elementi void come <input>
in <input />
, in quanto JSX è basato su XML e non su HTML.
Quando si usa hyperscript, spesso è necessario tradurre HTML in sintassi hyperscript per usarlo. Per accelerare questo processo, puoi utilizzare un convertitore HTML-to-Mithril-template creato dalla comunità per fare gran parte del lavoro per te.