JSX
説明
JSX は、JavaScript のコード中に HTML のタグを記述できる構文拡張です。JavaScript の標準仕様ではありませんが、アプリケーション開発において必須ではありません。しかし、JSX を利用することで、コードの可読性や保守性が向上し、開発効率を高めることができます。チームのコーディングスタイルに合わせて、導入を検討すると良いでしょう。
function MyComponent() {
return {
view: () => m('main', [m('h1', 'Hello world')]),
};
}
// 次のように記述できます:
function MyComponent() {
return {
view: () => (
<main>
<h1>Hello world</h1>
</main>
),
};
}
JSX 内では、中括弧 {}
を使用して JavaScript の式を埋め込むことができます。
var greeting = 'Hello';
var url = 'https://google.com';
var link = <a href={url}>{greeting}!</a>;
// <a href="https://google.com">Hello!</a> が生成されます
コンポーネントは、コンポーネント名の先頭を大文字にする規則に従うか、プロパティとしてアクセスすることで使用できます。
m.render(document.body, <MyComponent />)
// m.render(document.body, m(MyComponent)) と同等
<m.route.Link href="/home">Go home</m.route.Link>
// m(m.route.Link, {href: "/home"}, "Go home") と同等
セットアップ
JSX を使用する最も簡単な方法は、Babel プラグインを利用することです。
Babel の利用には npm が必要です。npm は Node.js をインストールする際に自動的にインストールされます。npm をインストール後、プロジェクトフォルダを作成し、以下のコマンドを実行してください。
npm init -y
Webpack と Babel を組み合わせて使用する場合は、以下のセクションを参照してください。
Babel をスタンドアロンツールとしてインストールするには、次のコマンドを実行します。
npm install @babel/core @babel/cli @babel/preset-env @babel/plugin-transform-react-jsx --save-dev
.babelrc
ファイルを作成します。
{
"presets": ["@babel/preset-env"],
"plugins": [
[
"@babel/plugin-transform-react-jsx",
{
"pragma": "m",
"pragmaFrag": "'['"
}
]
]
}
Babel を実行するための npm スクリプトを設定します。package.json
を開き、"scripts"
の下に次のエントリを追加します。
{
"name": "my-project",
"scripts": {
"babel": "babel src --out-dir bin --source-maps"
}
}
これで、次のコマンドを使用して Babel を実行できます。
npm run babel
Webpack で Babel を使用する
Webpack をまだバンドラーとしてインストールしていない場合は、次のコマンドを実行します。
npm install webpack webpack-cli --save-dev
次の手順に従って、Babel を Webpack に統合します。
npm install @babel/core babel-loader @babel/preset-env @babel/plugin-transform-react-jsx --save-dev
.babelrc
ファイルを作成します。
{
"presets": ["@babel/preset-env"],
"plugins": [
[
"@babel/plugin-transform-react-jsx",
{
"pragma": "m",
"pragmaFrag": "'['"
}
]
]
}
次に、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'],
},
};
すでに Webpack に詳しい方は、Babel のオプションを webpack.config.js
の babel-loader
セクションに記述するとエラーが発生するため、.babelrc
ファイルに記述する必要があることに注意してください。
この構成では、アプリケーションのエントリポイントとなるソースコードファイルが src/index.js
にあり、バンドルされたファイルが bin/app.js
に出力されることを前提としています。
バンドラーを実行するための npm スクリプトを設定します。package.json
を開き、"scripts"
の下に次のエントリを追加します。
{
"name": "my-project",
"scripts": {
"start": "webpack --mode development --watch"
}
}
コマンドラインから次を実行して、バンドラーを実行できます。
npm start
プロダクションビルド
ミニファイされたファイルを生成するには、package.json
を開き、build
という名前の新しい npm スクリプトを追加します。
{
"name": "my-project",
"scripts": {
"start": "webpack -d --watch",
"build": "webpack -p"
}
}
本番環境でフックを使用して、本番ビルドスクリプトを自動的に実行できます。Heroku の例を次に示します。
{
"name": "my-project",
"scripts": {
"start": "webpack -d --watch",
"build": "webpack -p",
"heroku-postbuild": "webpack -p"
}
}
m
をグローバルにアクセス可能にする
プロジェクト全体から m
(Mithril の関数) にグローバルにアクセスできるようにするには、まず webpack.config.js
で webpack
を次のようにインポートします。
const webpack = require('webpack');
次に、Webpack 構成オブジェクトの plugins
プロパティに新しいプラグインを作成します。
{
plugins: [
new webpack.ProvidePlugin({
m: 'mithril',
}),
];
}
ProvidePlugin
の詳細については、Webpack のドキュメントを参照してください。
React との違い
Mithril の JSX には、React の JSX と比較して、いくつかの微妙な違いがあります。
属性とスタイルのプロパティの命名規則
React では、data-*
および aria-*
属性を除き、HTML 属性名の代わりにキャメルケースの DOM プロパティ名を使用する必要があります。たとえば、class
の代わりに className
、for
の代わりに htmlFor
を使用します。一方、Mithril では、小文字の HTML 属性名を使用するのが一般的です。Mithril は、プロパティが存在しない場合に属性の設定にフォールバックするため、HTML とより直感的に整合性が取れます。ほとんどの場合、DOM プロパティと HTML 属性名は同じか、非常に似ています。たとえば、入力要素の value
/checked
や、HTML 要素の elem.tabIndex
プロパティに対する tabindex
グローバル属性などです。大文字小文字の違いを超えて異なることはまれです。class
属性の elem.className
プロパティや、for
属性の elem.htmlFor
プロパティは、数少ない例外です。
同様に、React は常に、elem.style
のプロパティを介して DOM で公開されるキャメルケースのスタイルプロパティ名(cssHeight
や backgroundColor
など)を使用します。Mithril は、キャメルケースとケバブケースの CSS プロパティ名(height
や background-color
など)の両方をサポートしており、慣例的に後者を優先します。cssHeight
、cssFloat
、およびベンダープレフィックス付きのプロパティのみが、大文字小文字の違いを超えて異なります。
DOM イベント
React では、すべてのイベントハンドラーの先頭の文字を大文字にします。onClick
は click
イベントをリッスンし、onSubmit
は submit
イベントをリッスンします。複数の単語が連結されている場合は、さらに変更されます。たとえば、onMouseMove
は mousemove
イベントをリッスンします。Mithril ではこのケースマッピングは行われず、代わりにネイティブイベントに on
を付与するだけです。したがって、onclick
および onmousemove
のリスナーを追加することで、それぞれ 2 つのイベントをリッスンできます。これは HTML の命名規則とより密接に対応しており、HTML またはバニラ JavaScript の DOM の知識がある場合は、より直感的です。
React は、キャプチャフェーズ中にイベントリスナーをスケジュールすることをサポートしています(デフォルトのバブリングフェーズが 2 回目のパスで内側から外側に向かうのとは対照的に、最初のパスで外側から内側に向かう)。Mithril には現在そのような機能はありませんが、将来的にサポートされる可能性があります。必要な場合は、ライフサイクルフックで独自のリスナーを手動で追加および削除できます。
JSX vs hyperscript
JSX と hyperscript は、仮想ノード (vnode) を記述するために使用できる 2 つの異なる構文であり、それぞれに異なる利点と欠点があります。
JSX は、HTML/XML の知識があり、その種の構文で DOM 要素を記述する方が快適な場合に、より扱いやすいでしょう。また、句読点が少なく、属性に含まれる視覚的なノイズが少ないため、よりクリーンな印象になり、読みやすいと感じる人も多いでしょう。そしてもちろん、多くの一般的なエディターは、HTML と同じように DOM 要素のオートコンプリートをサポートしています。ただし、使用するには追加のビルドステップが必要であり、エディターのサポートは通常の JS ほど広くなく、コードが冗長になる傾向があります。また、動的なコンテンツを多く扱う場合は、すべてに補間を使用する必要があるため、少し冗長になります。
Hyperscript は、HTML や XML をあまり使用しないバックエンド JS の経験がある場合に、より適しているでしょう。JSX よりも冗長性が少なく、より簡潔であり、静的なクラス、ID、およびその他の属性に対して CSS のような糖衣構文を提供します。また、ビルドステップなしで使用できますが、必要に応じて追加できます。また、何も「補間」する必要がないため、動的なコンテンツに直面した場合に、わずかに簡単に操作できます。ただし、簡潔さゆえに読みにくくなる場合があります。特に経験が浅く、フロントエンドの HTML/CSS/XML の知識がない場合は、読みにくくなる可能性があります。また、ID、クラス、属性などの hyperscript セレクターの一部を自動補完するプラグインは、今のところ一般的ではありません。
より複雑なツリーでは、利点と欠点がどのように作用するかを確認できます。たとえば、@dead-claudia による実際のプロジェクトから採用され、明確さと読みやすさのためにいくつかの変更が加えられた、この hyperscript ツリーを検討してください。
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 }),
]
)
)
),
]),
};
}
JSX を使用した上記のコードとまったく同じものを次に示します。この部分だけを見ても 2 つの構文がどのように異なるか、およびどのような利点と欠点が適用されるかを確認できます。
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>
),
};
}
ヒントとコツ
HTML を JSX に変換する
Mithril.js では、整形式の HTML は一般に有効な JSX です。通常は、attr=value
のように引用符で囲まれていないプロパティ値を attr="value"
に変更したり、<input>
のような空タグを <input />
に変更したりする程度です。これは、JSX が HTML ではなく XML に準拠しているためです。
hyperscript を使用する場合、HTML を hyperscript 構文に変換して使用する必要があることがよくあります。このプロセスをスピードアップするために、コミュニティが作成した HTML-to-Mithril-template コンバーターを使用して、変換作業の多くを自動化できます。