m(selector, attributes, children)
Описание
Создаёт HTML-элемент в view Mithril.js.
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 arguments |
возвращаемое значение | Vnode | Vnode |
Как это работает
Mithril.js предоставляет функцию hyperscript m()
, которая позволяет описывать любую HTML-структуру с использованием синтаксиса JavaScript. Она принимает строку 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')); // помещает <br> в <body>
Вызов m.render()
несколько раз не приводит к воссозданию DOM-дерева с нуля при каждом вызове. Вместо этого, изменения в DOM вносятся только тогда, когда это необходимо для соответствия виртуальному DOM-дереву. Такое поведение предпочтительно, потому что воссоздание DOM с нуля является ресурсозатратным и вызывает такие проблемы, как потеря фокуса в полях ввода. В отличие от этого, обновление DOM только там, где это необходимо, значительно быстрее и упрощает поддержку сложных пользовательских интерфейсов, обрабатывающих множество пользовательских сценариев.
Гибкость
Функция 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()
может быть любой CSS-селектор, описывающий HTML-элемент. Он принимает любые допустимые CSS-комбинации синтаксиса #
(id), .
(class) и []
(attribute).
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]');
Это также относится и к пользовательским элементам. Например, вы можете использовать A-Frame в Mithril.js без каких-либо проблем!
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
, у которого есть свойство getter/setter массива elem.whitelist
, вы можете сделать это, и это будет работать так, как вы ожидаете:
m('my-special-element', {
whitelist: [
'https://example.com',
'https://neverssl.com',
'https://google.com',
],
});
Если у вас есть классы или идентификаторы для этих элементов, сокращения по-прежнему работают так, как вы ожидаете. Чтобы привести еще один пример 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
), так и имена свойств style
DOM в стиле camel case (например, 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 автоматически выполнит перерисовку после завершения callback-функции (если используется m.mount
или m.route
вместо m.render
). Вы можете отключить автоматическую перерисовку для конкретного события, установив для него e.redraw = false
:
m('div', {
onclick: function (e) {
// Предотвратить автоматическую перерисовку
e.redraw = false;
},
});
Свойства
Mithril.js поддерживает функциональность DOM, доступную через свойства, такие как свойства selectedIndex
и value
элемента <select>
.
m('select', { selectedIndex: 0 }, [
m('option', 'Option A'),
m('option', 'Option B'),
]);
Компоненты
Компоненты позволяют инкапсулировать логику в отдельный модуль и использовать его, как если бы это был элемент. Они являются основой для создания больших, масштабируемых приложений.
Компонент - это JavaScript-объект, содержащий метод view
. Чтобы использовать компонент, передайте его в качестве первого аргумента в 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 отсоединяет DOM-элемент только после завершения promise. Этот метод вызывается только для элемента, отсоединенного от родительского DOM-элемента, но не для его дочерних элементов. |
onremove(vnode) | Выполняется до удаления DOM-элемента из документа. Если определен хук onbeforeremove , onremove вызывается после вызова done . Этот метод вызывается для элемента, отсоединенного от родительского элемента, и для всех его дочерних элементов. |
onbeforeupdate(vnode, old) | Выполняется перед onupdate , и если он возвращает false , он предотвращает diff для элемента и всех его дочерних элементов. |
Чтобы узнать больше о методах жизненного цикла, см. страницу методов жизненного цикла.
Ключи
Vnode в списке могут иметь специальный атрибут, называемый key
, который можно использовать для управления идентификацией DOM-элемента при изменении данных модели, генерирующих список vnode.
Как правило, 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.
Чтобы узнать больше о ключах, см. страницу ключей.
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, такие как if
или for
, внутри JavaScript-выражений. Предпочтительно вообще избегать использования этих операторов и вместо этого использовать только вышеуказанные конструкции, чтобы сохранить структуру шаблонов линейной и декларативной.
Преобразование HTML
В Mithril.js правильно сформированный HTML является допустимым JSX. Для интеграции независимо созданного HTML-файла в проект с использованием JSX требуется немного усилий, кроме копирования и вставки.
При использовании hyperscript необходимо преобразовать HTML в синтаксис hyperscript, прежде чем код будет запущен. Чтобы облегчить это, вы можете использовать конвертер HTML-в-Mithril-шаблон.
Избегайте анти-паттернов
Хотя 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 не сохраняются.
Документация по компонентам содержит более подробную информацию и пример этого анти-паттерна.