Переход с v1.x
Версия 2.x практически полностью обратно совместима с v1.x по API, однако существуют некоторые несовместимые изменения.
Присваивание значения vnode.state
В v1.x вы могли произвольно манипулировать vnode.state и присваивать ему любые значения. В v2.x попытка изменения vnode.state приведет к ошибке. Способ миграции зависит от конкретного случая, но в большинстве ситуаций достаточно заменить ссылки с vnode.state на vnode.state.foo, выбрав подходящее имя для foo (например, count, если это текущее значение счетчика).
v1.x
var Counter = {
oninit: function (vnode) {
vnode.state = 0;
},
view: function (vnode) {
return m('.counter', [
m(
'button',
{
onclick: function () {
vnode.state--;
},
},
'-'
),
vnode.state,
m(
'button',
{
onclick: function () {
vnode.state++;
},
},
'+'
),
]);
},
};v2.x
var Counter = {
oninit: function (vnode) {
vnode.state.count = 0;
},
view: function (vnode) {
return m('.counter', [
m(
'button',
{
onclick: function () {
vnode.state.count--;
},
},
'-'
),
vnode.state.count,
m(
'button',
{
onclick: function () {
vnode.state.count++;
},
},
'+'
),
]);
},
};Когда v1.0 была впервые выпущена, классовые и замыкающие компоненты еще не существовали, поэтому фреймворк просто использовал то, что ему было нужно, из vnode.tag. Эта деталь реализации позволяла вам это делать, и в некоторых местах документации это подразумевалось как допустимое. Теперь все иначе, и это немного упрощает управление с точки зрения реализации, поскольку есть только одна ссылка на состояние, а не две.
Изменения в якорях маршрутов
В v1.x вы использовали oncreate: m.route.link и, если ссылка могла измениться, onupdate: m.route.link. Это были хуки жизненного цикла для vnode, которые можно было маршрутизировать. В v2.x теперь используется компонент m.route.Link. Селектор можно указать через атрибут selector:, если вы использовали что-либо отличное от m("a", ...) Параметры можно указать через options:, вы можете отключить его через disabled:, и другие атрибуты можно указать inline, включая href: (обязательно). Сам selector: может содержать любой селектор, допустимый в качестве первого аргумента для m, а атрибуты [href=...] и [disabled] можно указать как в селекторе, так и в обычных параметрах.
v1.x
m('a', {
href: '/path',
oncreate: m.route.link,
});
m('button', {
href: '/path',
oncreate: m.route.link,
});
m('button.btn[href=/path]', {
oncreate: m.route.link,
});v2.x
m(m.route.Link, {
href: '/path',
});
m(m.route.Link, {
selector: 'button',
href: '/path',
});
m(m.route.Link, {
selector: 'button.btn[href=/path]',
});Изменения в обработке ошибок m.request
В v1.x m.request разбирал ошибки из JSON-ответов и присваивал свойства полученного объекта ответа объекту ошибки. Например, если вы получили ответ со статусом 403 и телом {"code": "backoff", "timeout": 1000}, у ошибки были бы два дополнительных свойства: err.code = "backoff" и err.timeout = 1000.
В v2.x ответ присваивается свойству response объекта ошибки, а свойство code содержит полученный код состояния. Таким образом, если вы получили ответ со статусом 403 и телом {"code": "backoff", "timeout": 1000}, ошибка будет иметь два свойства: err.response = {code: "backoff", timeout: 1000} и err.code = 403.
m.withAttr удален
В v1.x обработчики событий могли использовать oninput: m.withAttr("value", func) и аналогичные конструкции. В v2.x рекомендуется считывать значения непосредственно из цели события. Это хорошо сочеталось с потоками, но поскольку идиома m.withAttr("value", stream) не была настолько распространена, как m.withAttr("value", prop), m.withAttr потерял большую часть своей полезности и был удален.
v1.x
var value = '';
// In your view
m('input[type=text]', {
value: value(),
oninput: m.withAttr('value', function (v) {
value = v;
}),
});
// OR
var value = m.stream('');
// In your view
m('input[type=text]', {
value: value(),
oninput: m.withAttr('value', value),
});v2.x
var value = '';
// In your view
m('input[type=text]', {
value: value,
oninput: function (ev) {
value = ev.target.value;
},
});
// OR
var value = m.stream('');
// In your view
m('input[type=text]', {
value: value(),
oninput: function (ev) {
value(ev.target.value);
},
});m.route.prefix
В v1.x m.route.prefix была функцией, вызываемой через m.route.prefix(prefix). Теперь это свойство, которое устанавливается с помощью m.route.prefix = prefix.
v1.x
m.route.prefix('/root');v2.x
m.route.prefix = '/root';Параметры и тело запросов m.request/m.jsonp
data и useBody были реорганизованы в params (параметры запроса, интерполированные в URL и добавленные к запросу) и body (тело для отправки в базовом XHR). Это дает вам гораздо лучший контроль над фактическим отправленным запросом и позволяет как интерполировать параметры запроса с помощью POST-запросов, так и создавать GET-запросы с телами.
m.jsonp, не имеющий значимого "тела", просто использует params, поэтому переименование data в params достаточно для этого метода.
v1.x
m.request('https://example.com/api/user/:id', {
method: 'GET',
data: { id: user.id },
});
m.request('https://example.com/api/user/create', {
method: 'POST',
data: userData,
});v2.x
m.request('https://example.com/api/user/:id', {
method: 'GET',
params: { id: user.id },
});
m.request('https://example.com/api/user/create', {
method: 'POST',
body: userData,
});Path templates
В v1.x существовало три отдельных синтаксиса шаблонов путей, которые, хотя и были похожи, имели 2 отдельно разработанных синтаксиса и 3 разные реализации. Это было определено довольно произвольным образом, и параметры обычно не экранировались. Теперь все либо кодируется, если это :key, либо является неэкранированным, если это :key.... Если что-то неожиданно закодировано, используйте :path.... Это просто.
Конкретно, вот как это влияет на каждый метод:
URL-адреса m.request и m.jsonp, пути m.route.set
Компоненты пути в v2.x экранируются автоматически при интерполяции. Предположим, вы вызываете m.route.set("/user/:name/photos/:id", {name: user.name, id: user.id}). Ранее, если user был {name: "a/b", id: "c/d"}, это установило бы маршрут на /user/a%2Fb/photos/c/d, но теперь он установит его на /user/a%2Fb/photos/c%2Fd. Если вы намеренно хотите интерполировать ключ без экранирования, используйте вместо этого :key....
Ключи в v2.x не могут содержать символы . или -. В v1.x они могли содержать все, кроме /.
Интерполяции во встроенных строках запроса, например, в /api/search?q=:query, не выполняются в v2.x. Передавайте их через params с соответствующими именами ключей вместо указания их в строке запроса.
Route patterns m.route
Ключи пути в форме :key... возвращали свой URL, декодированный в v1.x, но возвращают raw URL в v2.x.
Ранее такие конструкции, как :key.md, ошибочно принимались, при этом значение результирующего параметра устанавливалось равным keymd: "...". Это больше не так - .md теперь является частью шаблона, а не имени.
Lifecycle call order
В v1.x хуки жизненного цикла атрибутов на vnode компонента вызывались до собственных хуков жизненного цикла компонента во всех случаях. В v2.x это относится только к onbeforeupdate. Поэтому вам может потребоваться соответствующим образом скорректировать свой код.
v1.x
var Comp = {
oncreate: function () {
console.log('Component oncreate');
},
view: function () {
return m('div');
},
};
m.mount(document.body, {
view: function () {
return m(Comp, {
oncreate: function () {
console.log('Attrs oncreate');
},
});
},
});
// Logs:
// Attrs oncreate
// Component oncreatev2.x
var Comp = {
oncreate: function () {
console.log('Component oncreate');
},
view: function () {
return m('div');
},
};
m.mount(document.body, {
view: function () {
return m(Comp, {
oncreate: function () {
console.log('Attrs oncreate');
},
});
},
});
// Logs:
// Component oncreate
// Attrs oncreateСинхронность m.redraw
m.redraw() в v2.x всегда асинхронна. Вы можете явно запросить синхронную перерисовку с помощью m.redraw.sync(), если в данный момент не происходит перерисовка.
Selector attribute precedence
В v1.x атрибуты селектора имели приоритет над атрибутами, указанными в объекте атрибутов. Например, m("[a=b]", {a: "c"}).attrs возвращал {a: "b"}.
В v2.x атрибуты, указанные в объекте атрибутов, имеют приоритет над атрибутами селектора. Например, m("[a=b]", {a: "c"}).attrs возвращает {a: "c"}.
Обратите внимание, что технически это возврат к поведению v0.2.x.
Children normalization
В v1.x дочерние элементы vnode компонента нормализовались, как и другие vnode. В v2.x это больше не так, и вам нужно будет планировать это соответствующим образом. Это не влияет на нормализацию, выполняемую при рендеринге.
m.request headers
В v1.x Mithril.js устанавливал эти два заголовка для всех запросов, отличных от GET, но только когда для useBody было установлено значение true (по умолчанию) и выполнялись другие перечисленные условия:
Content-Type: application/json; charset=utf-8для запросов с телами JSONAccept: application/json, text/*для запросов, ожидающих ответы JSON
В v2.x Mithril.js устанавливает первый заголовок для всех запросов с телами JSON, которые != null, и опускает его по умолчанию в противном случае. Это делается независимо от выбранного метода, в том числе и для GET-запросов.
Первый из двух заголовков, Content-Type, вызовет предварительный запрос CORS, поскольку он не является CORS-safelisted request header из-за указанного типа контента, и это может привести к новым ошибкам в зависимости от того, как CORS настроен на вашем сервере. Если у вас возникнут проблемы с этим, вам может потребоваться переопределить этот заголовок, передав headers: {"Content-Type": "text/plain"}. (Заголовок Accept не вызывает проблем, поэтому вам не нужно его переопределять.)
Единственные типы контента, которые спецификация Fetch позволяет избежать проверок предварительной выборки CORS, - это application/x-www-form-urlencoded, multipart/form-data и text/plain. Он не допускает ничего другого и намеренно запрещает JSON.
Query parameters in hash strings in routes
В v1.x вы могли указывать параметры запроса для маршрутов как в строке запроса, так и в строке хеша, поэтому m.route.set("/route?foo=1&bar=2"), m.route.set("/route?foo=1#bar=2") и m.route.set("/route#foo=1&bar=2") были эквивалентны, и атрибуты, извлеченные из них, были бы {foo: "1", bar: "2"}.
В v2.x содержимое строк хеша игнорируется, но сохраняется. Итак, атрибуты, извлеченные из каждого из них, будут следующими:
m.route.set("/route?foo=1&bar=2")→{foo: "1", bar: "2"}m.route.set("/route?foo=1#bar=2")→{foo: "1"}m.route.set("/route#foo=1&bar=2")→{}
Причина этого в том, что URL-адреса, такие как https://example.com/#!/route#key, технически недействительны согласно спецификации URL и даже были недействительны согласно предшествующему ей RFC, и это всего лишь причуда спецификации HTML, что они разрешены. (Спецификация HTML должна была с самого начала требовать, чтобы идентификаторы и фрагменты местоположения были действительными фрагментами URL, если она хотела следовать спецификации.)
Или, короче говоря, прекратите использовать недействительные URL-адреса!
Keys
В v1.x вы могли свободно смешивать vnode с ключами и без ключей. Если первый узел имеет ключ, выполняется сравнение по ключам, предполагая, что каждый элемент имеет ключ, и просто игнорируя пропуски по мере продвижения. В противном случае выполняется последовательное сравнение, и если узел имеет ключ, проверяется, не изменился ли он одновременно с проверкой тегов и тому подобного.
В v2.x списки дочерних элементов фрагментов и элементов должны быть либо все с ключами, либо все без ключей. Пропуски также считаются без ключей для целей этой проверки - она больше не игнорирует их.
Если вам нужно обойти это, используйте идиому фрагмента, содержащего один vnode, например, [m("div", {key: whatever})].
m.version removed
Он редко использовался, и вы всегда можете добавить его обратно самостоятельно. Вам следует предпочитать обнаружение функций, чтобы знать, какие функции доступны, и API v2.x разработан для лучшего обеспечения этого.