JSX
Popis
JSX je syntaktické rozšíření JavaScriptu, které umožňuje psát HTML tagy přímo v JavaScriptovém kódu. Není součástí standardu JavaScriptu a pro vytváření aplikací není nutné ho používat, ale pro vás nebo váš tým může být jeho použití příjemnější.
function MyComponent() {
return {
view: () => m('main', [m('h1', 'Hello world')]),
};
}
// lze zapsat jako:
function MyComponent() {
return {
view: () => (
<main>
<h1>Hello world</h1>
</main>
),
};
}
Při použití JSX lze do tagů vkládat JavaScriptové výrazy pomocí složených závorek:
var greeting = 'Hello';
var url = 'https://google.com';
var link = <a href={url}>{greeting}!</a>;
// vygeneruje <a href="https://google.com">Hello!</a>
Komponenty lze použít pomocí konvence velkého počátečního písmene názvu komponenty nebo přístupem k ní jako k vlastnosti:
m.render(document.body, <MyComponent />)
// ekvivalentní k m.render(document.body, m(MyComponent))
<m.route.Link href="/home">Go home</m.route.Link>
// ekvivalentní k m(m.route.Link, {href: "/home"}, "Go home")
Nastavení
Pro používání JSX je nejjednodušší použít plugin Babel.
Pro Babel je vyžadován npm, který se instaluje automaticky s Node.js. Po instalaci npm vytvořte složku projektu a spusťte tento příkaz:
npm init -y
Pokud chcete používat Webpack a Babel společně, přejděte do sekce níže.
Chcete-li nainstalovat Babel jako samostatný nástroj, použijte tento příkaz:
npm install @babel/core @babel/cli @babel/preset-env @babel/plugin-transform-react-jsx --save-dev
Vytvořte soubor .babelrc
:
{
"presets": ["@babel/preset-env"],
"plugins": [
[
"@babel/plugin-transform-react-jsx",
{
"pragma": "m",
"pragmaFrag": "'['"
}
]
]
}
Chcete-li spustit Babel, nastavte npm skript. Otevřete package.json
a přidejte tento záznam do sekce "scripts"
:
{
"name": "my-project",
"scripts": {
"babel": "babel src --out-dir bin --source-maps"
}
}
Nyní můžete spustit Babel pomocí tohoto příkazu:
npm run babel
Použití Babel s Webpack
Pokud jste ještě nenainstalovali Webpack jako bundler, použijte tento příkaz:
npm install webpack webpack-cli --save-dev
Babel můžete integrovat do Webpacku následujícími kroky:
npm install @babel/core babel-loader @babel/preset-env @babel/plugin-transform-react-jsx --save-dev
Vytvořte soubor .babelrc
:
{
"presets": ["@babel/preset-env"],
"plugins": [
[
"@babel/plugin-transform-react-jsx",
{
"pragma": "m",
"pragmaFrag": "'['"
}
]
]
}
Dále vytvořte soubor s názvem 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'],
},
};
Pokud již Webpack znáte, pamatujte, že přidání Babel možností přímo do sekce babel-loader
v webpack.config.js
způsobí chybu. Proto je nutné je definovat v samostatném souboru .babelrc
.
Tato konfigurace předpokládá, že zdrojový kód pro vstupní bod aplikace je v src/index.js
a výstupní balíček bude uložen do bin/app.js
.
Chcete-li spustit bundler, nastavte npm skript. Otevřete package.json
a přidejte tento záznam do sekce "scripts"
:
{
"name": "my-project",
"scripts": {
"start": "webpack --mode development --watch"
}
}
Nyní můžete spustit bundler spuštěním tohoto příkazu z příkazové řádky:
npm start
Produkční build
Chcete-li vygenerovat minimalizovaný soubor, otevřete package.json
a přidejte nový npm skript s názvem build
:
{
"name": "my-project",
"scripts": {
"start": "webpack -d --watch",
"build": "webpack -p"
}
}
Můžete použít hooks ve vašem produkčním prostředí ke spuštění produkčního build skriptu automaticky. Zde je příklad pro použití na Heroku:
{
"name": "my-project",
"scripts": {
"start": "webpack -d --watch",
"build": "webpack -p",
"heroku-postbuild": "webpack -p"
}
}
Zpřístupnění m
globálně
Aby bylo možné přistupovat k m
globálně z celého vašeho projektu, nejprve importujte webpack
do vašeho webpack.config.js
takto:
const webpack = require('webpack');
Poté vytvořte nový plugin ve vlastnosti plugins
konfiguračního objektu Webpack:
{
plugins: [
new webpack.ProvidePlugin({
m: 'mithril',
}),
];
}
Další informace o ProvidePlugin
najdete v dokumentaci Webpack.
Rozdíly oproti React
JSX v Mithril se v některých jemných, ale důležitých aspektech liší od JSX v React.
Konvence pro psaní velkých a malých písmen u atributů a vlastností stylu
React vyžaduje používání camelCase notace pro názvy DOM vlastností namísto HTML atributů, s výjimkou atributů data-*
a aria-*
. Například použití className
místo class
a htmlFor
místo for
. V Mithril se obvykle používají názvy HTML atributů psané malými písmeny. Mithril se vždy pokusí nastavit atribut, pokud vlastnost neexistuje, což je intuitivnější s HTML. Většinou jsou názvy vlastností DOM a HTML atributů buď stejné, nebo velmi podobné. Například value
/checked
pro vstupy a globální atribut tabindex
vs. vlastnost elem.tabIndex
na HTML elementech. Velmi zřídka se liší více než v psaní velkých a malých písmen: vlastnost elem.className
pro atribut class
nebo vlastnost elem.htmlFor
pro atribut for
patří mezi několik výjimek.
Podobně React vždy používá názvy vlastností stylu psané camelCase, které jsou zpřístupněny v DOM prostřednictvím vlastností elem.style
(jako cssHeight
a backgroundColor
). Mithril podporuje jak camelCase, tak názvy CSS vlastností psané kebab-case (jako height
a background-color
) a idiomaticky preferuje kebab-case. Pouze cssHeight
, cssFloat
a vlastnosti s prefixem dodavatele se liší ve více než v psaní velkých a malých písmen.
DOM události
React používá velká písmena pro první znak v názvech obslužných rutin událostí: onClick
pro událost click
, onSubmit
pro událost submit
. Některé jsou dále upraveny, protože se jedná o více slov spojených dohromady. Například onMouseMove
naslouchá událostem mousemove
. Mithril toto mapování nepoužívá. Místo toho jednoduše přidává předponu on
k názvu nativní události. Pro události click
a mousemove
byste tedy použili posluchače onclick
a onmousemove
. To mnohem více odpovídá schématu pojmenování HTML a je intuitivnější, pokud pocházíte z HTML nebo vanilla DOM prostředí.
React podporuje plánování posluchačů událostí během fáze zachycení (v prvním průchodu, zvenku dovnitř, na rozdíl od výchozí fáze bublání jdoucí dovnitř ven ve druhém průchodu) připojením Capture
k dané události. Mithril v současné době takovou funkčnost postrádá, ale v budoucnu by ji mohl získat. Pokud je to nutné, můžete ručně přidávat a odebírat vlastní posluchače v lifecycle hooks.
JSX vs hyperscript
JSX a hyperscript jsou dvě různé syntaxe pro definování vnode (virtuálních uzlů) a každá má své výhody a nevýhody:
JSX je přístupnější, pokud máte zkušenosti s HTML/XML a je vám pohodlnější specifikovat DOM elementy s tímto druhem syntaxe. V mnoha případech je také přehlednější, protože používá méně interpunkce a atributy jsou méně vizuálně rušivé, což usnadňuje čtení. A samozřejmě, mnoho běžných editorů poskytuje podporu automatického doplňování pro DOM elementy stejným způsobem, jako to dělají pro HTML. Vyžaduje však další krok sestavení, podpora editorů není tak široká jako u normálního JS a je výrazně více rozvláčný. Při práci s velkým množstvím dynamického obsahu může být také poněkud zdlouhavější, protože vyžaduje interpolaci pro všechny dynamické hodnoty.
Hyperscript je přístupnější, pokud máte zkušenosti s backendovým JS prostředím, které nezahrnuje mnoho HTML nebo XML. Je stručnější s menší redundancí a poskytuje cukr podobný CSS pro statické třídy, ID a další atributy. Lze jej také použít bez jakéhokoli kroku sestavení, ačkoli můžete jeden přidat, pokud si přejete. A je o něco snazší s ním pracovat při práci s velkým množstvím dynamického obsahu, protože nemusíte nic "interpolovat". Pro některé uživatele, zejména pro ty s menšími zkušenostmi s front-end vývojem (HTML/CSS/XML), může být stručnost hyperscriptu hůře čitelná. Navíc nevím o žádných pluginech, které by automaticky doplňovaly části hyperscript selektorů, jako jsou ID, třídy a atributy.
Můžete vidět, jak se kompromisy projevují ve složitějších stromech. Například zvažte tento hyperscript strom, adaptovaný ze skutečného projektu od @dead-claudia s některými úpravami pro jasnost a čitelnost:
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 }),
]
)
)
),
]),
};
}
Zde je přesný ekvivalent výše uvedeného kódu pomocí JSX. Můžete vidět, jak se obě syntaxe liší a jaké kompromisy platí.
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>
),
};
}
Tipy a triky
Převod HTML na JSX
V Mithril.js je dobře formátované HTML obecně platné JSX. Obvykle stačí jen vložit nezpracované HTML a vše by mělo fungovat. Jediné věci, které byste obvykle museli upravit, jsou hodnoty vlastností bez uvozovek, jako je attr=value
, na attr="value"
a změnit prázdné elementy jako <input>
na <input />
, což je způsobeno tím, že JSX je založeno na XML a ne na HTML.
Při použití hyperscriptu často potřebujete převést HTML na syntaxi hyperscriptu, abyste jej mohli použít. Chcete-li tento proces urychlit, můžete použít komunitní nástroj pro převod HTML do šablon Mithril, který automatizuje většinu konverze.