Migracja z v1.x
Wersja 2.x jest w dużej mierze kompatybilna z API wersji 1.x, ale wprowadza pewne zmiany powodujące niezgodność wsteczną (breaking changes).
Przypisywanie do vnode.state
W wersji 1.x można było manipulować vnode.state
i przypisywać do niego dowolne wartości. W wersji 2.x próba modyfikacji spowoduje błąd. Migracja zależy od konkretnego przypadku użycia, ale w większości sytuacji wystarczy zmienić odwołania z vnode.state
na vnode.state.foo
, wybierając odpowiednią nazwę dla foo
(np. count
, jeśli reprezentuje aktualną wartość licznika).
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++;
},
},
'+'
),
]);
},
};
W początkowych wersjach v1.0 komponenty klasowe i oparte na domknięciach nie istniały, więc dane pobierano z vnode.tag
. Ten szczegół implementacyjny to umożliwiał i był sugerowany w dokumentacji. Obecnie sytuacja jest inna, co ułatwia zarządzanie, ponieważ istnieje tylko jedno odniesienie do stanu, a nie dwa.
Zmiany w kotwicach routingu
W wersji 1.x używano oncreate: m.route.link
i, opcjonalnie, onupdate: m.route.link
jako hooków cyklu życia dla routowalnych vnode. W wersji 2.x używany jest komponent m.route.Link
. Selektor można określić za pomocą atrybutu selector:
, jeśli używano selektora innego niż m("a", ...)
. Opcje można określić za pomocą options:
, można go wyłączyć za pomocą disabled:
, a inne atrybuty można określić inline, w tym href:
(wymagane). Sam selector:
może zawierać dowolny selektor akceptowalny jako pierwszy argument dla m
, a atrybuty [href=...]
i [disabled]
można określić zarówno w selektorze, jak i w opcjach.
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]',
});
Zmiany w obsłudze błędów m.request
W wersji 1.x, m.request
parsował błędy z odpowiedzi JSON i przypisywał właściwości sparsowanego obiektu do obiektu błędu. Na przykład, jeśli otrzymano odpowiedź ze statusem 403 i treścią {"code": "backoff", "timeout": 1000}
, obiekt błędu miałby dwie właściwości: err.code = "backoff"
i err.timeout = 1000
.
W wersji 2.x odpowiedź jest przypisywana do właściwości response
w obiekcie błędu, a właściwość code
zawiera kod statusu HTTP. Tak więc, jeśli otrzymano odpowiedź ze statusem 403 i treścią {"code": "backoff", "timeout": 1000}
, obiekt błędu miałby dwie właściwości: err.response = {code: "backoff", timeout: 1000}
i err.code = 403
.
Usunięcie m.withAttr
W wersji 1.x, event listenery mogły używać oninput: m.withAttr("value", func)
i podobnych konstrukcji. W wersji 2.x, wartości należy odczytywać bezpośrednio z targetu zdarzenia. Współpracowało to dobrze ze strumieniami danych, ale ponieważ idiom m.withAttr("value", stream)
nie był tak powszechny jak m.withAttr("value", prop)
, m.withAttr
straciło na użyteczności i zostało usunięte.
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);
},
});
Zmiana m.route.prefix
W wersji 1.x, m.route.prefix
było funkcją wywoływaną za pomocą m.route.prefix(prefix)
. Teraz jest to właściwość, którą ustawia się za pomocą m.route.prefix = prefix
.
v1.x
m.route.prefix('/root');
v2.x
m.route.prefix = '/root';
Parametry i ciało żądania m.request
/m.jsonp
data
i useBody
zostały zastąpione przez params
(parametry zapytania interpolowane do URL i dołączane do żądania) oraz body
(ciało do wysłania w bazowym XHR). Daje to znacznie lepszą kontrolę nad wysyłanym żądaniem i pozwala zarówno na interpolację parametrów zapytania w żądaniach POST
, jak i tworzenie żądań GET
z ciałami.
m.jsonp
, nie mając sensownego "ciała", po prostu używa params
, więc zmiana nazwy data
na params
jest wystarczająca dla tej metody.
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,
});
Szablony ścieżek (Path templates)
W wersji 1.x istniały trzy oddzielne składnie szablonów ścieżek, które, choć podobne, miały 2 oddzielnie zaprojektowane składnie i 3 różne implementacje. Definicja była dość ad-hocowa, a parametry na ogół nie były escape'owane. Teraz, jeśli używasz :key
, wszystko jest kodowane, a jeśli :key...
, wszystko jest surowe. Jeśli coś jest nieoczekiwanie kodowane, użyj :path...
. To proste.
Konkretnie, oto jak wpływa to na każdą metodę:
Adresy URL m.request
i m.jsonp
, ścieżki m.route.set
W wersji 2.x składniki ścieżki są automatycznie escape'owane podczas interpolacji. Załóżmy, że wywołujesz m.route.set("/user/:name/photos/:id", {name: user.name, id: user.id})
. Wcześniej, jeśli user
był {name: "a/b", id: "c/d"}
, ustawiłoby to trasę na /user/a%2Fb/photos/c/d
, ale teraz ustawi ją na /user/a%2Fb/photos/c%2Fd
. Jeśli chcesz celowo interpolować klucz bez escape'owania, użyj :key...
.
Klucze w wersji 2.x nie mogą zawierać żadnych wystąpień .
ani -
. W wersji 1.x mogły zawierać wszystko oprócz /
.
Interpolacje w inline query strings (ciągach zapytań w linii), jak w /api/search?q=:query
, nie są wykonywane w wersji 2.x. Przekazuj je zamiast tego za pomocą params
z odpowiednimi nazwami kluczy, bez określania ich w ciągu zapytania.
Wzorce tras m.route
Klucze ścieżki w formie :key...
zwracały swój URL zdekodowany w wersji 1.x, ale zwracają surowy URL w wersji 2.x.
Wcześniej, rzeczy takie jak :key.md
były błędnie akceptowane, a wartość wynikowego parametru była ustawiana na keymd: "..."
. To już nie ma miejsca - .md
jest teraz częścią wzorca, a nie nazwą.
Kolejność wywołań cyklu życia (Lifecycle call order)
W wersji 1.x hooki cyklu życia atrybutów na vnode komponentu były wywoływane przed hookami cyklu życia samego komponentu. W wersji 2.x, ma to miejsce tylko dla onbeforeupdate
. Może być konieczne odpowiednie dostosowanie kodu.
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
Synchroniczność m.redraw
m.redraw()
w wersji 2.x jest zawsze asynchroniczne. Możesz konkretnie zażądać synchronicznego przerysowania za pomocą m.redraw.sync()
, pod warunkiem że aktualnie nie trwa przerysowywanie.
Kolejność atrybutów selektora
W wersji 1.x, atrybuty selektora miały pierwszeństwo przed atrybutami określonymi w obiekcie atrybutów. Na przykład, m("[a=b]", {a: "c"}).attrs
zwracało {a: "b"}
.
W wersji 2.x, atrybuty określone w obiekcie atrybutów mają pierwszeństwo przed atrybutami selektora. Na przykład, m("[a=b]", {a: "c"}).attrs
zwraca {a: "c"}
.
Zauważ, że technicznie jest to powrót do zachowania z wersji 0.2.x.
Normalizacja dzieci
W wersji 1.x, dzieci vnode komponentu były normalizowane jak inne vnode. W wersji 2.x, to już nie ma miejsca i należy to uwzględnić w kodzie. Nie wpływa to na normalizację wykonywaną podczas renderowania.
Nagłówki m.request
W wersji 1.x, Mithril.js ustawiał te dwa nagłówki dla wszystkich żądań innych niż GET
, ale tylko wtedy, gdy useBody
było ustawione na true
(domyślnie) i spełnione były pozostałe wymienione warunki:
Content-Type: application/json; charset=utf-8
dla żądań z ciałami JSONAccept: application/json, text/*
dla żądań oczekujących odpowiedzi JSON
W wersji 2.x, Mithril.js ustawia nagłówek Content-Type: application/json; charset=utf-8
dla wszystkich żądań z ciałami JSON, które nie są null
i pomija go w przeciwnym razie. Dzieje się to niezależnie od wybranej metody, w tym w żądaniach GET
.
Ustawienie nagłówka Content-Type
może wywołać preflight CORS, ponieważ nie jest to nagłówek żądania bezpieczny dla CORS ze względu na określony typ zawartości, a to może wprowadzić nowe błędy w zależności od konfiguracji CORS na serwerze. Jeśli napotkasz problemy z tym związane, może być konieczne zastąpienie tego nagłówka, przekazując headers: {"Content-Type": "text/plain"}
. (Nagłówek Accept
nie wywołuje preflight, więc nie trzeba go zastępować.)
Specyfikacja Fetch pozwala uniknąć sprawdzania preflight CORS tylko dla typów zawartości application/x-www-form-urlencoded
, multipart/form-data
i text/plain
. Nie pozwala na nic innego i celowo zabrania JSON.
Parametry zapytania w ciągach hash w trasach
W wersji 1.x można było określić parametry zapytania dla tras zarówno w ciągu zapytania, jak i w ciągu hash, więc m.route.set("/route?foo=1&bar=2")
, m.route.set("/route?foo=1#bar=2")
i m.route.set("/route#foo=1&bar=2")
były równoważne, a atrybuty z nich wyodrębnione byłyby {foo: "1", bar: "2"}
.
W wersji 2.x, zawartość ciągów hash jest ignorowana, ale pozostaje zachowana. Tak więc atrybuty wyodrębnione z każdego z nich byłyby następujące:
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")
→{}
Powodem tego jest to, że adresy URL takie jak https://example.com/#!/route#key
są technicznie nieprawidłowe zgodnie ze specyfikacją URL i były nawet nieprawidłowe zgodnie z RFC, które ją poprzedzało, i to tylko dziwactwo specyfikacji HTML, że są dozwolone. (Specyfikacja HTML powinna była wymagać, aby identyfikatory i fragmenty lokalizacji były od początku prawidłowymi fragmentami URL, jeśli chciała postępować zgodnie ze specyfikacją.)
Lub krótko mówiąc, przestań używać nieprawidłowych adresów URL!
Klucze
W wersji 1.x można było swobodnie mieszać vnode z kluczami i bez kluczy. Jeśli pierwszy węzeł ma klucz, wykonywane jest różnicowanie z kluczami, zakładając, że każdy element ma klucz i po prostu ignorując puste miejsca po drodze. W przeciwnym razie wykonywane jest różnicowanie iteracyjne, a jeśli węzeł ma klucz, sprawdzane jest, czy nie zmienił się w tym samym czasie, w którym sprawdzane są tagi i tym podobne.
W wersji 2.x, listy dzieci zarówno fragmentów, jak i elementów muszą być albo wszystkie z kluczami, albo wszystkie bez kluczy. Puste węzły są również uważane za nieposiadające kluczy na potrzeby tego sprawdzenia - nie są już ignorowane.
Jeśli musisz to obejść, użyj idiomu fragmentu zawierającego pojedynczy vnode, takiego jak [m("div", {key: whatever})]
.
Usunięcie m.version
Ogólnie rzecz biorąc, służyło to niewielu celom i zawsze możesz dodać to z powrotem samodzielnie. Powinieneś preferować wykrywanie możliwości, aby wiedzieć, jakie funkcje są dostępne, a API wersji 2.x zostało zaprojektowane tak, aby lepiej to umożliwić.