m(selector, attributes, children)
描述
表示 Mithril.js 視圖中的 HTML 元素
m('div.foo', { style: { color: 'red' } }, 'hello');
// 渲染為以下 HTML:
// <div class="foo" style="color: red">hello</div>
您也可以使用類似 HTML 的語法,稱為 JSX,並使用 Babel 將其轉換為等效的 hyperscript 調用。這與上面的範例等效。
<div class="foo" style="color: red">
hello
</div>
簽章
vnode = m(selector, attrs, children)
參數 | 類型 | 是否必填 | 描述 |
---|---|---|---|
selector | String|Object|Function | 是 | CSS 選擇器或元件 |
attrs | Object | 否 | HTML 屬性或元素屬性 |
children | Array<Vnode>|String|Number|Boolean | 否 | 子 vnode。可以寫成 splat 參數 |
回傳值 | Vnode | vnode |
運作原理
Mithril.js 提供了一個 hyperscript 函數 m()
,允許使用 JavaScript 語法表達任何 HTML 結構。它接受一個 selector
字串(必填)、一個 attrs
物件(可選)和一個 children
陣列(可選)。
m('div', { id: 'box' }, 'hello');
// 渲染為以下 HTML:
// <div id="box">hello</div>
m()
函數實際上並不會回傳 DOM 元素。相反地,它會返回一個 虛擬 DOM 節點,或稱 vnode,它是一個 JavaScript 物件,表示要創建的 DOM 元素。
// 一個 vnode
var vnode = {
tag: 'div',
attrs: { id: 'box' },
children: [
/*...*/
],
};
若要將 vnode 轉換為實際的 DOM 元素,請使用 m.render()
函數:
m.render(document.body, m('br')); // 在 <body> 中放入一個 <br>
多次調用 m.render()
不會每次都從頭重新創建 DOM 樹。相反地,每次調用只會在絕對必要時才對 DOM 樹進行變更,以反映傳入調用的虛擬 DOM 樹。這種行為是可取的,因為從頭重新創建 DOM 非常耗費資源,並且會導致諸如失去輸入焦點等問題。相比之下,僅在必要時更新 DOM 速度更快,並且更容易維護處理多個使用者情境的複雜 UI。
彈性
m()
函數既是 多型態的 也是 可變參數的。換句話說,它在期望的輸入參數方面非常靈活:
// 簡單標籤
m('div'); // <div></div>
// 屬性和子節點是可選的
m('a', { id: 'b' }); // <a id="b"></a>
m('span', 'hello'); // <span>hello</span>
// 具有子節點的標籤
m('ul', [
// <ul>
m('li', 'hello'), // <li>hello</li>
m('li', 'world'), // <li>world</li>
]); // </ul>
// 陣列是可選的
m(
'ul', // <ul>
m('li', 'hello'), // <li>hello</li>
m('li', 'world') // <li>world</li>
); // </ul>
CSS 選擇器
m()
的第一個參數可以是任何可以描述 HTML 元素的 CSS 選擇器。它接受 #
(id)、.
(class) 和 []
(attribute) 語法的任何有效 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'
);
// 渲染為以下 HTML:
// <a href="/" class="link selected">Home</a>
作為第二個參數傳遞的屬性
您可以在第二個可選的參數中傳遞屬性、特性、事件和生命週期鉤子(詳情請參閱下一節)。
m("button", {
class: "my-button",
onclick: function() {/* ... */},
oncreate: function() {/* ... */}
})
如果此類屬性的值為 null
或 undefined
,則將其視為該屬性不存在。
如果 m()
的第一個和第二個參數中都有類別名稱,則它們會如您所期望的那樣合併在一起。如果第二個參數中類別的值為 null
或 undefined
,則會忽略它。
如果另一個屬性同時存在於第一個和第二個參數中,則第二個參數優先,即使它是 null
或 undefined
。
DOM 屬性
Mithril.js 使用 JavaScript API 和 DOM API (setAttribute
) 來解析屬性。這表示您可以使用兩種語法來引用屬性。
例如,在 JavaScript API 中,readonly
屬性稱為 element.readOnly
(請注意大寫)。在 Mithril.js 中,支援以下所有內容:
m('input', { readonly: true }); // 小寫
m('input', { readOnly: true }); // 大寫
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',
}),
]);
對於自訂元素,它不會自動字串化屬性,以防它們是物件、數字或某些其他非字串值。因此,假設您有一個自訂元素 my-special-element
,它具有一個 elem.whitelist
陣列 getter/setter 屬性,您可以執行此操作,並且它會如您所期望的那樣工作:
m('my-special-element', {
whitelist: [
'https://example.com',
'https://neverssl.com',
'https://google.com',
],
});
如果您有這些元素的類別或 ID,則速記仍然可以如您所期望的那樣工作。若要提取另一個 A-Frame 範例:
// 這兩個是等效的
m('a-entity#player');
m('a-entity', { id: 'player' });
請注意,所有具有特殊語義的屬性,例如生命週期屬性、onevent
處理器、key
、class
和 style
,它們的處理方式與普通 HTML 元素相同。
Style 屬性
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 不會嘗試將單位新增至數字值。它只是將它們字串化。
事件
Mithril.js 支持所有 DOM 事件的事件處理常式繫結,包括其規範未定義 on${event}
屬性的事件,例如 touchstart
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.mount
或 m.route
而不是直接使用 m.render
)。您可以透過在事件上設定 e.redraw = false
來專門針對單個事件停用自動重新繪製:
m('div', {
onclick: function (e) {
// 阻止自動重新繪製
e.redraw = false;
},
});
屬性
Mithril.js 支援可透過屬性存取的 DOM 功能,例如 <select>
的 selectedIndex
和 value
屬性。
m('select', { selectedIndex: 0 }, [
m('option', 'Option A'),
m('option', 'Option B'),
]);
元件
元件 允許您將邏輯封裝到一個單元中,並像使用元素一樣使用它。它們是製作大型、可擴展應用程式的基礎。
元件是任何包含 view
方法的 JavaScript 物件。若要使用元件,請將元件作為第一個參數傳遞給 m()
,而不是傳遞 CSS 選擇器字串。您可以透過定義屬性和子節點將參數傳遞給元件,如下例所示。
// 定義一個元件
var Greeter = {
view: function (vnode) {
return m('div', vnode.attrs, ['Hello ', vnode.children]);
},
};
// 使用它
m(Greeter, { style: 'color:red;' }, 'world');
// 渲染為以下 HTML:
// <div style="color:red;">Hello world</div>
若要瞭解有關元件的更多資訊,請參閱元件頁面。
生命週期方法
Vnode 和元件可以具有生命週期方法(也稱為 hooks),這些方法在 DOM 元素的生命週期中的各個點調用。Mithril.js 支持的生命週期方法有:oninit
、oncreate
、onupdate
、onbeforeremove
、onremove
和 onbeforeupdate
。
生命週期方法的定義方式與 DOM 事件處理常式相同,但接收 vnode 作為參數,而不是 Event 物件:
function initialize(vnode) {
console.log(vnode);
}
m('div', { oninit: initialize });
Hook | 描述 |
---|---|
oninit(vnode) | 在將 vnode 渲染到實際 DOM 元素之前執行 |
oncreate(vnode) | 在將 vnode 附加到 DOM 後執行 |
onupdate(vnode) | 每次在 DOM 元素附加到文件時發生重新繪製時執行 |
onbeforeremove(vnode) | 在從文件中移除 DOM 元素之前執行。如果回傳 Promise,則 Mithril.js 僅在 Promise 完成後才分離 DOM 元素。此方法僅在從其父 DOM 元素分離的元素上觸發,但不在其子元素上觸發。 |
onremove(vnode) | 在從文件中移除 DOM 元素之前執行。如果定義了 onbeforeremove hook,則在調用 done 後調用 onremove 。此方法在從其父元素分離的元素及其所有子元素上觸發 |
onbeforeupdate(vnode, old) | 在 onupdate 之前執行,如果它回傳 false ,則會阻止元素及其所有子元素的差異比較 |
若要瞭解有關生命週期方法的更多資訊,請參閱生命週期方法頁面。
Keys
列表中的 Vnode 可以具有一個稱為 key
的特殊屬性,該屬性可用於在產生 vnode 列表的模型數據變更時管理 DOM 元素的識別。
通常,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));
具有 key 表示如果 users
陣列被打亂並且重新渲染視圖,則輸入將以完全相同的順序被打亂,以便保持正確的焦點和 DOM 狀態。
若要瞭解有關 key 的更多資訊,請參閱 key 頁面。
SVG 和 MathML
Mithril.js 完全支持 SVG。也支持 Xlink,但與 Mithril.js 的 v1.0 之前的版本不同,必須明確定義命名空間:
m('svg', [m("image[xlink:href='image.gif']")]);
MathML 也完全支持。
使範本動態化
由於巢狀 vnode 只是普通的 JavaScript 表達式,因此您可以簡單地使用 JavaScript 功能來操作它們
動態文字
var user = { name: 'John' };
m('.name', user.name); // <div class="name">John</div>
迴圈
使用 Array
方法,例如 map
來迭代數據列表
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)
// ))
條件式
使用三元運算子有條件地在視圖上設定內容
var isError = false;
m('div', isError ? 'An error occurred' : 'Saved'); // <div>Saved</div>
您不能在 JavaScript 表達式中使用 JavaScript 語句,例如 if
或 for
。最好完全避免使用這些語句,而是專門使用上面的結構,以便保持範本的結構線性且宣告式。
轉換 HTML
在 Mithril.js 中,格式良好的 HTML 是有效的 JSX。除了複製貼上之外,幾乎不需要任何努力即可將獨立產生的 HTML 文件整合到使用 JSX 的項目中。
使用 hyperscript 時,必須先將 HTML 轉換為 hyperscript 語法,然後才能執行程式碼。為了方便起見,您可以使用 HTML-to-Mithril-template 轉換器。
避免反面模式
雖然 Mithril.js 具有彈性,但不鼓勵使用某些代碼模式:
避免動態選擇器
不同的 DOM 元素具有不同的屬性,並且通常具有不同的行為。使選擇器可配置可能會將元件的實現細節洩漏出其單元。
// 避免
var BadInput = {
view: function (vnode) {
return m('div', [m('label'), m(vnode.attrs.type || 'input')]);
},
};
您應該明確地編碼每個有效的可能性,或重構代碼的變化部分,而不是使選擇器動態化。
// 偏好明確代碼
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')]);
},
};
// 偏好重構變異性
var BetterLabeledComponent = {
view: function (vnode) {
return m('div', [m('label', vnode.attrs.title), vnode.children]);
},
};
避免在視圖之外建立 vnode
當重新繪製遇到一個與先前渲染中的 vnode 完全相等的 vnode 時,它將被跳過,並且其內容將不會被更新。雖然這看起來像是效能優化的機會,但應避免這樣做,因為它會阻止該節點樹中的動態變更 - 這會導致諸如下游生命週期方法無法在重新繪製時觸發的副作用。從這個意義上說,Mithril.js vnode 是不可變的:新的 vnode 會與舊的 vnode 進行比較;對 vnode 的變更不會持久化。
元件文件包含更多詳細資訊和此反面模式的範例。