Переход с 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 oncreate
v2.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 разработан для лучшего обеспечения этого.