m(selector, attributes, children)
Description
Mithril.js のビューにおける HTML 要素を表現します。
m('div.foo', { style: { color: 'red' } }, 'hello');
// renders to this HTML:
// <div class="foo" style="color: red">hello</div>
Babel を使用して、同等の hyperscript 呼び出しに変換する JSX と呼ばれる HTML のような構文を使用することもできます。これは上記と同等です。
<div class="foo" style="color: red">
hello
</div>
Signature
vnode = m(selector, attrs, children)
Argument | Type | Required | Description |
---|---|---|---|
selector | String|Object|Function | Yes | CSS セレクターまたは コンポーネントです。 |
attrs | Object | No | HTML 属性または要素プロパティ |
children | Array<Vnode>|String|Number|Boolean | No | 子 vnode。スプレッド引数として記述可能です。 |
returns | Vnode | vnode |
How it works
Mithril.js は、JavaScript 構文を使用して任意の HTML 構造を表現できる hyperscript 関数 m()
を提供します。これは、selector
文字列 (必須)、attrs
オブジェクト (オプション)、および children
配列 (オプション) を受け取ります。
m('div', { id: 'box' }, 'hello');
// renders to this HTML:
// <div id="box">hello</div>
m()
関数は、実際には DOM 要素を返しません。代わりに、作成される DOM 要素を表す JavaScript オブジェクトである 仮想 DOM ノード、すなわち vnode を返します。
// a vnode
var vnode = {
tag: 'div',
attrs: { id: 'box' },
children: [
/*...*/
],
};
vnode を実際の DOM 要素に変換するには、m.render()
関数を使用します。
m.render(document.body, m('br')); // puts a <br> in <body>
m.render()
を複数回呼び出しても、DOM ツリーが毎回最初から再作成されるわけではありません。代わりに、各呼び出しは、呼び出しに渡された仮想 DOM ツリーを反映するために必要な場合にのみ、DOM ツリーに変更を加えます。DOM を最初から再作成するのは非常にコストがかかり、入力フォーカスの喪失などの問題が発生するため、この動作は推奨されます。対照的に、必要な場合にのみ DOM を更新する方がはるかに高速であり、複数のユーザーストーリーを処理する複雑な UI を維持しやすくなります。
Flexibility
m()
関数は、_ポリモーフィック_であり、_可変引数_です。つまり、入力パラメータとして期待されるものに非常に柔軟に対応できます。
// simple tag
m('div'); // <div></div>
// attributes and children are optional
m('a', { id: 'b' }); // <a id="b"></a>
m('span', 'hello'); // <span>hello</span>
// tag with child nodes
m('ul', [
// <ul>
m('li', 'hello'), // <li>hello</li>
m('li', 'world'), // <li>world</li>
]); // </ul>
// array is optional
m(
'ul', // <ul>
m('li', 'hello'), // <li>hello</li>
m('li', 'world') // <li>world</li>
); // </ul>
CSS selectors
m()
の最初の引数には、HTML 要素を記述できる任意の CSS セレクターを指定できます。#
(id)、.
(class)、および []
(属性) 構文の有効な CSS の組み合わせを受け入れます。
m('div#hello');
// <div id="hello"></div>
m('section.container');
// <section class="container"></section>
m('input[type=text][placeholder=Name]');
// <input type="text" placeholder="Name" />
m("a#exit.external[href='https://example.com']", 'Leave');
// <a id="exit" class="external" href="https://example.com">Leave</a>
タグ名を省略すると、Mithril.js は div
タグと見なします。
m('.box.box-bordered'); // <div class="box box-bordered"></div>
通常、静的属性(つまり、値が変化しない属性)には CSS セレクターを使用し、動的な属性値には属性オブジェクトを渡すことをお勧めします。
var currentURL = '/';
m(
'a.link[href=/]',
{
class: currentURL === '/' ? 'selected' : '',
},
'Home'
);
// renders to this HTML:
// <a href="/" class="link selected">Home</a>
Attributes passed as the second argument
属性、プロパティ、イベント、およびライフサイクルフックは、2 番目のオプション引数として渡すことができます(詳細については、次のセクションを参照)。
m("button", {
class: "my-button",
onclick: function() {/* ... */},
oncreate: function() {/* ... */}
})
このような属性の値が null
または undefined
の場合、その属性は存在しないものとして扱われます。
m()
の最初と 2 番目の引数の両方にクラス名がある場合、予想どおりにそれらがマージされます。2 番目の引数のクラスの値が null
または undefined
の場合、無視されます。
別の属性が最初と 2 番目の引数の両方に存在する場合、2 番目の引数が null
または undefined
であっても優先されます。
DOM attributes
Mithril.js は、JavaScript API と DOM API (setAttribute
) の両方を使用して属性を解決します。つまり、両方の構文を使用して属性を参照できます。
例えば、JavaScript API では、readonly
属性は element.readOnly
と呼ばれます(大文字表記に注意してください)。Mithril.js では、次のすべてがサポートされています。
m('input', { readonly: true }); // lowercase
m('input', { readOnly: true }); // uppercase
m('input[readonly]');
m('input[readOnly]');
これにはカスタム要素も含まれています。たとえば、Mithril.js 内で A-Frame を問題なく使用できます。
m('a-scene', [
m('a-box', {
position: '-1 0.5 -3',
rotation: '0 45 0',
color: '#4CC3D9',
}),
m('a-sphere', {
position: '0 1.25 -5',
radius: '1.25',
color: '#EF2D5E',
}),
m('a-cylinder', {
position: '1 0.75 -3',
radius: '0.5',
height: '1.5',
color: '#FFC65D',
}),
m('a-plane', {
position: '0 0 -4',
rotation: '-90 0 0',
width: '4',
height: '4',
color: '#7BC8A4',
}),
m('a-sky', {
color: '#ECECEC',
}),
]);
カスタム要素の場合、プロパティがオブジェクト、数値、またはその他の非文字列値である場合に備えて、プロパティを自動的に文字列化しません。したがって、elem.whitelist
配列の getter/setter プロパティを持つカスタム要素 my-special-element
があると仮定すると、これを行うことができ、期待どおりに動作します。
m('my-special-element', {
whitelist: [
'https://example.com',
'https://neverssl.com',
'https://google.com',
],
});
これらの要素にクラスまたは ID がある場合、短縮形は期待どおりに機能します。別の A-Frame の例を挙げると:
// These two are equivalent
m('a-entity#player');
m('a-entity', { id: 'player' });
ライフサイクル属性、onevent
ハンドラー、key
、class
、style
などのマジックセマンティクスを持つすべてのプロパティは、通常の HTML 要素の場合と同じように扱われることに注意してください。
Style attribute
Mithril.js は、文字列とオブジェクトの両方を有効な style
値としてサポートしています。つまり、次のすべてがサポートされています。
m('div', { style: 'background:red;' });
m('div', { style: { background: 'red' } });
m('div[style=background:red]');
style
に文字列を使用すると、値が変更された CSS ルールだけでなく、要素内のすべてのインラインスタイルが再描画時に上書きされます。
ハイフンで区切られた CSS プロパティ名 (例: background-color
) と、キャメルケースの DOM style
プロパティ名 (例: backgroundColor
) の両方を使用できます。ブラウザがサポートしている場合は、CSS カスタムプロパティ を定義することもできます。
Mithril.js は、数値に単位を追加することはありません。単に文字列化するだけです。
Events
Mithril.js は、touchstart
など、仕様で on${event}
プロパティが定義されていないイベントを含む、すべての DOM イベントのイベントハンドラーバインディングをサポートしています。
function doSomething(e) {
console.log(e);
}
m('div', { onclick: doSomething });
Mithril.js は、関数と EventListener オブジェクトを受け入れます。したがって、これも機能します。
var clickListener = {
handleEvent: function (e) {
console.log(e);
},
};
m('div', { onclick: clickListener });
デフォルトでは、hyperscript でアタッチされたイベントが発生すると、イベントコールバックが返された後、Mithril.js の自動再描画がトリガーされます(m.render
を直接使用するのではなく、m.mount
または m.route
を使用している場合)。e.redraw = false
を設定することで、単一のイベントに対してのみ自動再描画を無効にできます。
m('div', {
onclick: function (e) {
// Prevent auto-redraw
e.redraw = false;
},
});
Properties
Mithril.js は、<select>
の selectedIndex
や value
プロパティなど、プロパティを通じてアクセスできる DOM 機能をサポートしています。
m('select', { selectedIndex: 0 }, [
m('option', 'Option A'),
m('option', 'Option B'),
]);
Components
コンポーネントを使用すると、ロジックをユニットにカプセル化し、要素のように扱うことができます。これらは、大規模でスケーラブルなアプリケーションを作成するための基盤となります。
コンポーネントは、view
メソッドを含む任意の JavaScript オブジェクトです。コンポーネントを使用するには、CSS セレクター文字列を渡す代わりに、コンポーネントを m()
の最初の引数として渡します。以下の例に示すように、属性と子を定義して、コンポーネントに引数を渡すことができます。
// define a component
var Greeter = {
view: function (vnode) {
return m('div', vnode.attrs, ['Hello ', vnode.children]);
},
};
// consume it
m(Greeter, { style: 'color:red;' }, 'world');
// renders to this HTML:
// <div style="color:red;">Hello world</div>
コンポーネントの詳細については、コンポーネントのページ を参照してください。
Lifecycle methods
Vnode とコンポーネントは、ライフサイクルメソッド(別名 hooks)を持つことができます。これらは、DOM 要素のライフサイクル中のさまざまな時点で呼び出されます。Mithril.js でサポートされているライフサイクルメソッドは、oninit
、oncreate
、onupdate
、onbeforeremove
、onremove
、および onbeforeupdate
です。
ライフサイクルメソッドは、DOM イベントハンドラーと同じ方法で定義されますが、Event オブジェクトの代わりに vnode を引数として受け取ります。
function initialize(vnode) {
console.log(vnode);
}
m('div', { oninit: initialize });
Hook | Description |
---|---|
oninit(vnode) | vnode が実際の DOM 要素にレンダリングされる前に実行されます。 |
oncreate(vnode) | vnode が DOM に追加された後に実行されます。 |
onupdate(vnode) | DOM 要素がドキュメントにアタッチされている間、再描画が発生するたびに実行されます。 |
onbeforeremove(vnode) | DOM 要素がドキュメントから削除される前に実行されます。Promise が返された場合、Mithril.js は Promise が完了した後にのみ DOM 要素をデタッチします。このメソッドは、親 DOM 要素からデタッチされた要素でのみトリガーされ、その子要素ではトリガーされません。 |
onremove(vnode) | DOM 要素がドキュメントから削除される前に実行されます。onbeforeremove フックが定義されている場合、onremove は done が呼び出された後に呼び出されます。このメソッドは、親要素からデタッチされた要素と、そのすべての子要素でトリガーされます。 |
onbeforeupdate(vnode, old) | onupdate の前に実行され、false を返すと、要素とそのすべての子要素の差分処理が防止されます。 |
ライフサイクルメソッドの詳細については、ライフサイクルメソッドのページ を参照してください。
Keys
リスト内の Vnode は、key
と呼ばれる特別な属性を持つことができます。これは、vnode リストを生成するモデルデータが変更されたときに、DOM 要素の ID を管理するために使用できます。
通常、key
は、データ配列内のオブジェクトの一意な識別子フィールドであるべきです。
var users = [
{ id: 1, name: 'John' },
{ id: 2, name: 'Mary' },
];
function userInputs(users) {
return users.map(function (u) {
return m('input', { key: u.id }, u.name);
});
}
m.render(document.body, userInputs(users));
キーを持つということは、users
配列がシャッフルされ、ビューが再レンダリングされた際に、入力が全く同じ順序でシャッフルされ、正しいフォーカスと DOM 状態が維持されることを意味します。
key の詳細については、key のページ を参照してください。
SVG and MathML
Mithril.js は SVG を完全にサポートしています。Xlink もサポートされていますが、Mithril.js の v1.0 より前のバージョンとは異なり、名前空間を明示的に定義する必要があります。
m('svg', [m("image[xlink:href='image.gif']")]);
MathML も完全にサポートされています。
Making templates dynamic
ネストされた vnode は単なるプレーンな JavaScript 式であるため、JavaScript の機能を使用してそれらを操作できます。
Dynamic text
var user = { name: 'John' };
m('.name', user.name); // <div class="name">John</div>
Loops
map
などの Array
メソッドを使用して、データのリストを反復処理します。
var users = [{ name: 'John' }, { name: 'Mary' }];
m(
'ul',
users.map(function (u) {
// <ul>
return m('li', u.name); // <li>John</li>
// <li>Mary</li>
})
); // </ul>
// ES6+:
// m("ul", users.map(u =>
// m("li", u.name)
// ))
Conditionals
三項演算子を使用して、ビューにコンテンツを条件付きで設定します。
var isError = false;
m('div', isError ? 'An error occurred' : 'Saved'); // <div>Saved</div>
JavaScript 式内で if
や for
などの JavaScript ステートメントを使用することはできません。これらのステートメントを完全に使用しないようにし、代わりに上記の構成のみを使用して、テンプレートの構造を線形かつ宣言的に保つことをお勧めします。
Converting HTML
Mithril.js では、整形式 HTML は有効な JSX です。コピー&ペースト以外にほとんど手間はかかりません。
hyperscript を使用する場合は、コードを実行する前に HTML を hyperscript 構文に変換する必要があります。これを容易にするために、HTML-to-Mithril-template converter を使用できます。
Avoid Anti-patterns
Mithril.js は柔軟性がありますが、一部のコードパターンは推奨されません。
Avoid dynamic selectors
DOM 要素が異なれば属性も異なり、多くの場合、動作も異なります。セレクターを動的にすると、コンポーネントの実装の詳細がユニット外に漏洩する可能性があります。
// AVOID
var BadInput = {
view: function (vnode) {
return m('div', [m('label'), m(vnode.attrs.type||'input')]);
},
};
セレクターを動的にする代わりに、有効な可能性を明示的にコーディングするか、可変部分をリファクタリングすることを推奨します。
// PREFER explicit code
var BetterInput = {
view: function (vnode) {
return m('div', [m('label', vnode.attrs.title), m('input')]);
},
};
var BetterSelect = {
view: function (vnode) {
return m('div', [m('label', vnode.attrs.title), m('select')]);
},
};
// PREFER refactor variability out
var BetterLabeledComponent = {
view: function (vnode) {
return m('div', [m('label', vnode.attrs.title), vnode.children]);
},
};
Avoid creating vnodes outside views
再描画時に、前回のレンダリング時の vnode と厳密に等しい vnode が検出されると、スキップされ、そのコンテンツは更新されません。これはパフォーマンスの最適化の機会のように思えるかもしれませんが、そのノードツリーの動的な変更を防ぐため、避ける必要があります。これにより、再描画でトリガーに失敗するダウンストリームのライフサイクルメソッドなどの副作用が発生します。この意味で、Mithril.js vnode は不変です。新しい vnode は古い vnode と比較されます。vnode への変更は永続化されません。
コンポーネントのドキュメントには、このアンチパターンの詳細と例 が含まれています。