JSX
Opis JSX
JSX to rozszerzenie składni JavaScript, które pozwala na pisanie kodu przypominającego HTML wewnątrz plików JavaScript. Nie jest to wymagane do tworzenia aplikacji Mithril.js, ale może zwiększyć czytelność i ułatwić pisanie komponentów, w zależności od preferencji Twoich lub Twojego zespołu.
function MyComponent() {
return {
view: () => m('main', [m('h1', 'Hello world')]),
};
}
// Można to zapisać również tak:
function MyComponent() {
return {
view: () => (
<main>
<h1>Hello world</h1>
</main>
),
};
}
Używając JSX, możesz wstawiać wyrażenia JavaScript wewnątrz tagów JSX za pomocą nawiasów klamrowych:
var greeting = 'Hello';
var url = 'https://google.com';
var link = <a href={url}>{greeting}!</a>;
// zwraca <a href="https://google.com">Hello!</a>
Komponenty mogą być używane poprzez zastosowanie konwencji pisania nazwy komponentu wielką literą lub poprzez dostęp do niego jako właściwości:
m.render(document.body, <MyComponent />)
// równoważne z m.render(document.body, m(MyComponent))
<m.route.Link href="/home">Go home</m.route.Link>
// równoważne z m(m.route.Link, {href: "/home"}, "Go home")
Konfiguracja
Najprostszym sposobem na używanie JSX jest użycie wtyczki Babel.
Babel wymaga npm, który jest instalowany automatycznie podczas instalacji Node.js. Po zainstalowaniu npm utwórz folder projektu i uruchom następujące polecenie:
npm init -y
Jeśli chcesz używać Webpacka i Babel razem, przejdź do sekcji poniżej.
Aby zainstalować Babel jako samodzielne narzędzie, użyj tego polecenia:
npm install @babel/core @babel/cli @babel/preset-env @babel/plugin-transform-react-jsx --save-dev
Utwórz plik .babelrc
:
{
"presets": ["@babel/preset-env"],
"plugins": [
[
"@babel/plugin-transform-react-jsx",
{
"pragma": "m",
"pragmaFrag": "'['"
}
]
]
}
Aby uruchomić Babel, skonfiguruj skrypt w npm. Otwórz plik package.json
i dodaj następujący wpis w sekcji "scripts"
:
{
"name": "my-project",
"scripts": {
"babel": "babel src --out-dir bin --source-maps"
}
}
Możesz teraz uruchomić Babel za pomocą tego polecenia:
npm run babel
Używanie Babel z Webpackiem
Jeśli nie masz jeszcze zainstalowanego Webpacka jako bundlera, użyj tego polecenia:
npm install webpack webpack-cli --save-dev
Aby zintegrować Babel z Webpackiem, wykonaj następujące kroki:
npm install @babel/core babel-loader @babel/preset-env @babel/plugin-transform-react-jsx --save-dev
Utwórz plik .babelrc
:
{
"presets": ["@babel/preset-env"],
"plugins": [
[
"@babel/plugin-transform-react-jsx",
{
"pragma": "m",
"pragmaFrag": "'['"
}
]
]
}
Następnie utwórz plik o nazwie 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'],
},
};
Dla osób zaznajomionych z Webpackiem: dodanie opcji Babel do sekcji babel-loader
w pliku webpack.config.js
spowoduje błąd, dlatego należy dołączyć je w oddzielnym pliku .babelrc
.
Ta konfiguracja zakłada, że plik kodu źródłowego dla punktu wejścia aplikacji znajduje się w src/index.js
, a wygenerowany pakiet zostanie umieszczony w bin/app.js
.
Aby uruchomić bundler, skonfiguruj skrypt w npm. Otwórz plik package.json
i dodaj następujący wpis w sekcji "scripts"
:
{
"name": "my-project",
"scripts": {
"start": "webpack --mode development --watch"
}
}
Możesz teraz uruchomić bundler, uruchamiając następujące polecenie:
npm start
Kompilacja produkcyjna
Aby wygenerować zminifikowany plik, otwórz package.json
i dodaj nowy skrypt npm o nazwie build
:
{
"name": "my-project",
"scripts": {
"start": "webpack -d --watch",
"build": "webpack -p"
}
}
W środowisku produkcyjnym możesz użyć hooków, aby automatycznie uruchamiać skrypt kompilacji produkcyjnej. Oto przykład dla Heroku:
{
"name": "my-project",
"scripts": {
"start": "webpack -d --watch",
"build": "webpack -p",
"heroku-postbuild": "webpack -p"
}
}
Udostępnianie m
globalnie
Aby uzyskać dostęp do m
globalnie w całym projekcie, najpierw zaimportuj webpack
w swoim pliku webpack.config.js
w następujący sposób:
const webpack = require('webpack');
Następnie utwórz nową wtyczkę w sekcji plugins
obiektu konfiguracji Webpack:
{
plugins: [
new webpack.ProvidePlugin({
m: 'mithril',
}),
];
}
Zobacz dokumentację Webpacka, aby uzyskać więcej informacji na temat ProvidePlugin
.
Różnice w porównaniu do React
JSX w Mithril.js ma subtelne, ale istotne różnice w porównaniu do JSX w React.
Konwencje dotyczące wielkości liter atrybutów i właściwości stylów
W React wymagane są nazwy właściwości DOM (zapisywane notacją camelCase) zamiast nazw atrybutów HTML, z wyjątkiem atrybutów data-*
i aria-*
. Na przykład, użycie className
zamiast class
i htmlFor
zamiast for
. W Mithril.js zaleca się używanie nazw atrybutów HTML pisanych małymi literami. Mithril.js zawsze próbuje ustawić atrybut, jeśli właściwość nie istnieje, co jest bardziej intuicyjne w odniesieniu do HTML. Należy zauważyć, że w większości przypadków nazwy właściwości DOM i atrybutów HTML są takie same lub bardzo podobne. Na przykład value
/checked
dla pól wejściowych i atrybut globalny tabindex
w porównaniu z właściwością elem.tabIndex
na elementach HTML. Bardzo rzadko różnią się one czymś więcej niż wielkością liter: właściwość elem.className
dla atrybutu class
lub właściwość elem.htmlFor
dla atrybutu for
należą do nielicznych wyjątków.
Podobnie, React zawsze używa nazw właściwości stylów pisanych notacją camelCase, udostępnianych w DOM za pośrednictwem właściwości elem.style
(takich jak cssHeight
i backgroundColor
). Mithril.js obsługuje zarówno ten sposób, jak i nazwy właściwości CSS pisane kebab-case (takie jak height
i background-color
) i preferuje te drugie. Tylko cssHeight
, cssFloat
i właściwości z prefiksem dostawcy różnią się czymś więcej niż wielkością liter.
Zdarzenia DOM
W React pierwszy znak w nazwach obsługi zdarzeń jest pisany wielką literą: onClick
obsługuje zdarzenia click
, a onSubmit
zdarzenia submit
. Niektóre są dodatkowo zmieniane, ponieważ są to wielokrotne słowa połączone ze sobą. Na przykład onMouseMove
obsługuje zdarzenia mousemove
. Mithril.js nie wykonuje tego mapowania wielkości liter, ale zamiast tego po prostu dodaje on
do nazwy natywnego zdarzenia, więc dodajesz nasłuchiwacze dla onclick
i onmousemove
, aby nasłuchiwać odpowiednio tych dwóch zdarzeń. Odpowiada to znacznie ściślej schematowi nazewnictwa HTML i jest bardziej intuicyjne, jeśli masz doświadczenie w HTML lub vanilla DOM.
React obsługuje planowanie nasłuchiwaczy zdarzeń podczas fazy przechwytywania (w pierwszym przebiegu, od zewnątrz do wewnątrz, w przeciwieństwie do domyślnej fazy bąbelkowania, przechodzącej od wewnątrz na zewnątrz w drugim przebiegu), dodając Capture
do nazwy zdarzenia. Mithril.js obecnie nie ma takiej funkcjonalności, ale może ją zyskać w przyszłości. Jeśli jest to konieczne, możesz ręcznie dodawać i usuwać własne nasłuchiwacze w hookach cyklu życia.
JSX vs hyperscript
JSX i hyperscript to dwie różne składnie, których można użyć do definiowania vnode'ów i mają one różne zalety i wady:
Jeśli znasz HTML/XML, JSX będzie bardziej przystępny, ponieważ pozwala na definiowanie elementów DOM za pomocą podobnej składni. Jest również nieco czystszy w wielu przypadkach, ponieważ używa mniej interpunkcji, a atrybuty zawierają mniej szumu wizualnego, więc wielu osobom wydaje się znacznie łatwiejszy do odczytania. Wiele popularnych edytorów zapewnia obsługę autouzupełniania dla elementów DOM w taki sam sposób, jak w przypadku HTML. Wymaga jednak dodatkowego kroku kompilacji, aby go użyć, obsługa edytora nie jest tak szeroka, jak w przypadku normalnego JS, i jest nieco bardziej rozwlekły. Jest również nieco bardziej rozwlekły, gdy masz do czynienia z dużą ilością dynamicznej zawartości, ponieważ musisz używać interpolacji do wszystkiego.
Jeśli masz doświadczenie w backendzie JS i nie znasz dobrze HTML/XML, hyperscript może być dla Ciebie bardziej przystępny. Jest bardziej zwięzły z mniejszą redundancją i zapewnia cukier składniowy podobny do CSS dla statycznych klas, identyfikatorów i innych atrybutów. Można go również używać bez żadnego kroku kompilacji, chociaż możesz dodać jeden, jeśli chcesz. Jest nieco łatwiejszy w pracy w obliczu dużej ilości dynamicznej zawartości, ponieważ nie musisz niczego "interpolować". Jednak zwięzłość utrudnia czytanie niektórym osobom, zwłaszcza tym mniej doświadczonym i pochodzącym z front-endowego tła HTML/CSS/XML, i nie znam żadnych wtyczek, które automatycznie uzupełniają części selektorów hyperscript, takie jak identyfikatory, klasy i atrybuty.
Możesz zobaczyć, jak te zalety i wady wpływają na bardziej złożone drzewa. Na przykład, rozważ to drzewo hyperscript, zaadaptowane z rzeczywistego projektu przez @dead-claudia z pewnymi zmianami dla jasności i czytelności:
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 }),
]
)
)
),
]),
};
}
Oto dokładny odpowiednik powyższego kodu, używający zamiast tego JSX. Możesz zobaczyć, jak te dwie składnie różnią się tylko w tym fragmencie kodu i jakie zalety i wady mają zastosowanie.
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>
),
};
}
Porady i triki
Konwertowanie HTML na JSX
W Mithril.js poprawnie sformatowany HTML jest zazwyczaj poprawnym JSX. Zazwyczaj wystarczy zmienić wartości atrybutów bez cudzysłowów (np. attr=value
na attr="value"
) oraz zamknąć tagi elementów bez zawartości (np. <input>
na <input />
), ponieważ JSX bazuje na XML, a nie na HTML.
Używając hyperscript, często musisz przetłumaczyć HTML na składnię hyperscript, aby go użyć. Aby przyspieszyć ten proces, możesz użyć stworzonego przez społeczność konwertera HTML na szablony Mithril, aby zrobić to za Ciebie.