Migration von v1.x
v2.x ist weitgehend API-kompatibel mit v1.x, es gibt jedoch einige Breaking Changes.
Zuweisung zu vnode.state
In v1.x war es möglich, vnode.state
zu manipulieren und beliebige Werte zuzuweisen. In v2.x führt eine solche Änderung zu einem Fehler. Die Migration kann unterschiedlich aussehen, aber in den meisten Fällen genügt es, Referenzen von vnode.state
zu vnode.state.foo
zu ändern und einen passenden Namen für foo
zu wählen (z. B. count
, wenn es sich um den aktuellen Wert eines Zählers handelt).
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++;
},
},
'+'
),
]);
},
};
Als v1.0 veröffentlicht wurde, gab es noch keine Klassen- oder Closure-Komponenten. Daher wurde einfach das aus vnode.tag
übernommen, was benötigt wurde. Dieses Implementierungsdetail ermöglichte diese Vorgehensweise, und einige Entwickler begannen, sich darauf zu verlassen. Die Dokumentation deutete dies an einigen Stellen ebenfalls an. Inzwischen hat sich dies geändert, was die Verwaltung aus Implementierungssicht vereinfacht, da es nur noch eine Referenz zum Status gibt, nicht zwei.
Änderungen an Routen-Ankern
In v1.x wurden oncreate: m.route.link
und, falls sich der Link ändern konnte, auch onupdate: m.route.link
als Lifecycle-Hooks auf dem VNode verwendet, der geroutet werden sollte. In v2.x wird stattdessen die m.route.Link
Komponente verwendet. Der Selektor kann über das Attribut selector:
angegeben werden, falls etwas anderes als m("a", ...)
verwendet wird. Optionen können über options:
angegeben werden. Die Komponente kann über disabled:
deaktiviert werden, und andere Attribute können inline angegeben werden, einschließlich href:
(erforderlich). Der selector:
selbst kann ein beliebiger Selektor sein, der als erstes Argument für m
gültig ist, und die Attribute [href=...]
und [disabled]
können sowohl im Selektor als auch in den normalen Optionen angegeben werden.
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]',
});
Änderungen an m.request
Fehlern
In v1.x hat m.request
Fehler von JSON-Aufrufen geparst und die Eigenschaften des resultierenden geparsten Antwortobjekts dem Fehler zugewiesen. Wenn also eine Antwort mit dem Status 403 und einem Body von {"code": "backoff", "timeout": 1000}
empfangen wurde, hätte der Fehler zwei zusätzliche Eigenschaften gehabt: err.code = "backoff"
und err.timeout = 1000
.
In v2.x wird die Antwort einer response
-Eigenschaft im Fehler zugewiesen, und eine code
-Eigenschaft enthält den resultierenden Statuscode. Wenn also eine Antwort mit dem Status 403 und einem Body von {"code": "backoff", "timeout": 1000}
empfangen wurde, wären dem Fehler zwei Eigenschaften zugewiesen worden: err.response = {code: "backoff", timeout: 1000}
und err.code = 403
.
m.withAttr
entfernt
In v1.x konnten Ereignis-Listener oninput: m.withAttr("value", func)
und Ähnliches verwenden. In v2.x werden diese Werte direkt aus dem Ziel des Events gelesen. Dies harmonierte gut mit Streams, aber da das Idiom m.withAttr("value", stream)
nicht annähernd so verbreitet war wie m.withAttr("value", prop)
, verlor m.withAttr
den größten Teil seines Nutzens und wurde daher entfernt.
v1.x
var value = '';
// In Ihrer View
m('input[type=text]', {
value: value(),
oninput: m.withAttr('value', function (v) {
value = v;
}),
});
// ODER
var value = m.stream('');
// In Ihrer View
m('input[type=text]', {
value: value(),
oninput: m.withAttr('value', value),
});
v2.x
var value = '';
// In Ihrer View
m('input[type=text]', {
value: value,
oninput: function (ev) {
value = ev.target.value;
},
});
// ODER
var value = m.stream('');
// In Ihrer View
m('input[type=text]', {
value: value(),
oninput: function (ev) {
value(ev.target.value);
},
});
m.route.prefix
In v1.x war m.route.prefix
eine Funktion, die mit m.route.prefix(prefix)
aufgerufen wurde. Es ist jetzt eine Eigenschaft, die über m.route.prefix = prefix
gesetzt wird.
v1.x
m.route.prefix('/root');
v2.x
m.route.prefix = '/root';
m.request
/m.jsonp
params und body
Die Parameter data
und useBody
wurden umbenannt und neu definiert. data
wurde zu params
, welches Query-Parameter enthält, die in die URL interpoliert und an die Anfrage angehängt werden. useBody
wurde zu body
, welcher den Inhalt enthält, der im zugrunde liegenden XHR gesendet werden soll. Dies ermöglicht eine bessere Kontrolle über die tatsächlich gesendete Anfrage und erlaubt es, sowohl Query-Parameter mit POST
-Anfragen zu interpolieren als auch GET
-Anfragen mit Inhalten zu erstellen.
m.jsonp
, das keinen sinnvollen "Body" hat, verwendet nur params
, sodass das Umbenennen von data
in params
für diese Methode ausreicht.
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 (Pfadvorlagen)
In v1.x gab es drei separate Path Template-Syntaxen, die zwar ähnlich waren, aber 2 separat entworfene Syntaxen und 3 verschiedene Implementierungen hatten. Die Definition war eher ad hoc, und Parameter wurden im Allgemeinen nicht maskiert. In v2.x wird alles entweder kodiert, wenn es :key
ist, oder roh, wenn es :key...
ist. Wenn Werte unerwartet kodiert werden, verwenden Sie :path...
. So einfach ist das.
Konkret wirkt sich dies wie folgt auf jede Methode aus:
m.request
und m.jsonp
URLs, m.route.set
Pfade
In v2.x werden Pfadkomponenten automatisch maskiert, wenn sie interpoliert werden. Angenommen, Sie rufen m.route.set("/user/:name/photos/:id", {name: user.name, id: user.id})
auf. Wenn user
zuvor {name: "a/b", id: "c/d"}
war, würde dies die Route auf /user/a%2Fb/photos/c/d
setzen, aber jetzt wird sie auf /user/a%2Fb/photos/c%2Fd
gesetzt. Wenn Sie einen Schlüssel absichtlich unmaskiert interpolieren möchten, verwenden Sie stattdessen :key...
.
Keys in v2.x dürfen keine Instanzen von .
oder -
enthalten. In v1.x konnten sie alles außer /
enthalten.
Interpolationen in Inline-Query-Strings, wie in /api/search?q=:query
, werden in v2.x nicht durchgeführt. Übergeben Sie diese stattdessen über params
mit den entsprechenden Key-Namen, ohne sie in der Query-String anzugeben.
m.route
Routenmuster
Pfadschlüssel der Form :key...
geben in v1.x ihre URL dekodiert zurück, geben aber in v2.x die rohe URL zurück.
In früheren Versionen wurden Konstrukte wie :key.md
fälschlicherweise akzeptiert, wobei der Wert des Parameters dann keymd: "..."
war. Dies ist nicht mehr der Fall: .md
ist nun Teil des Musters und nicht mehr des Namens.
Lifecycle Call Order (Reihenfolge der Lebenszyklusaufrufe)
In v1.x wurden Lebenszyklus-Hooks für Attribute auf Komponenten-VNodes in allen Fällen vor den eigenen Lifecycle-Hooks der Komponente aufgerufen. In v2.x ist dies nur für onbeforeupdate
der Fall. Sie müssen möglicherweise Ihren Code entsprechend anpassen.
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
Synchronität
m.redraw()
ist in v2.x immer asynchron. Ein synchrones Neuzeichnen kann explizit über m.redraw.sync()
angefordert werden, vorausgesetzt, es läuft gerade kein Neuzeichnen.
Selector Attribute Precedence (Vorrang von Selektorattributen)
In v1.x hatten Selektorattribute Vorrang vor Attributen, die im Attribut-Objekt angegeben wurden. Beispielsweise gab m("[a=b]", {a: "c"}).attrs
{a: "b"}
zurück.
In v2.x haben Attribute, die im Attribut-Objekt angegeben sind, Vorrang vor Selektorattributen. Beispielsweise gibt m("[a=b]", {a: "c"}).attrs
{a: "c"}
zurück.
Beachten Sie, dass dies technisch gesehen eine Rückkehr zum Verhalten von v0.2.x ist.
Children Normalization (Kindnormalisierung)
In v1.x wurden Component-VNode-Children wie andere VNodes normalisiert. In v2.x ist dies nicht mehr der Fall, und Sie müssen dies bei der Entwicklung berücksichtigen. Dies betrifft nicht die Normalisierung, die beim Rendern durchgeführt wird.
m.request
Headers (Header)
In v1.x setzte Mithril.js diese beiden Header bei allen Nicht-GET
-Anfragen, aber nur, wenn useBody
auf true
gesetzt war (Standardeinstellung) und die anderen aufgeführten Bedingungen erfüllt waren:
Content-Type: application/json; charset=utf-8
für Anfragen mit JSON-BodiesAccept: application/json, text/*
für Anfragen, die JSON-Antworten erwarten
In v2.x setzt Mithril.js den Content-Type
Header für alle Anfragen mit JSON-Bodies, die nicht null
sind. Andernfalls wird er standardmäßig weggelassen. Dies geschieht unabhängig von der gewählten Methode, auch bei GET
-Anfragen.
Der erste Header, Content-Type
, löst einen CORS-Prefetch aus, da er aufgrund des angegebenen Content-Type kein CORS-safelisted Request Header ist (https://fetch.spec.whatwg.org/#cors-safelisted-request-header). Dies könnte neue Fehler verursachen, je nachdem, wie CORS auf Ihrem Server konfiguriert ist. Wenn Sie auf Probleme stoßen, können Sie diesen Header überschreiben, indem Sie headers: {"Content-Type": "text/plain"}
übergeben. Der Accept
-Header muss nicht überschrieben werden, da er keine Probleme verursacht.
Die Fetch-Spezifikation erlaubt nur die Content-Types application/x-www-form-urlencoded
, multipart/form-data
und text/plain
ohne CORS-Prefetch-Prüfung. JSON ist explizit ausgeschlossen.
Query Parameters (Abfrageparameter) in Hash Strings (Hash-Zeichenketten) in Routen (Routen)
In v1.x konnten Query Parameters für Routen sowohl in der Query String als auch in der Hash String angegeben werden, sodass m.route.set("/route?foo=1&bar=2")
, m.route.set("/route?foo=1#bar=2")
und m.route.set("/route#foo=1&bar=2")
alle gleichwertig waren und die daraus extrahierten Attribute {foo: "1", bar: "2"}
gewesen wären.
In v2.x wird der Inhalt von Hash Strings ignoriert, aber beibehalten. Die Attribute, die aus jedem extrahiert würden, wären also diese:
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")
→{}
Der Grund ist, dass URLs wie https://example.com/#!/route#key
technisch ungültig sind.
Oder kurz gesagt: Verwenden Sie keine ungültigen URLs mehr!
Keys (Schlüssel)
In v1.x konnten Keyed und Unkeyed VNodes frei gemischt werden. Wenn der erste Node Keyed ist, wird ein Keyed Diff durchgeführt, wobei davon ausgegangen wird, dass jedes Element einen Key hat und Löcher einfach ignoriert werden. Andernfalls wird ein iterativer Diff durchgeführt, und wenn ein Node einen Key hat, wird überprüft, ob er sich nicht gleichzeitig geändert hat, wenn Tags und ähnliches überprüft werden.
In v2.x müssen Children Lists von Fragments und Elements entweder alle mit Schlüssel versehen oder alle ohne Schlüssel versehen sein. Löcher gelten für die Zwecke dieser Prüfung auch als Unkeyed - sie werden nicht mehr ignoriert.
Um dies zu umgehen, kann ein Fragment mit einem einzelnen VNode verwendet werden, z.B. [m("div", {key: whatever})]
.
m.version
entfernt
Es hatte im Allgemeinen wenig Nutzen und kann bei Bedarf selbst wieder hinzugefügt werden. Es wird empfohlen, Feature Detection zu verwenden, um die verfügbaren Funktionen zu ermitteln. Die v2.x-API ist darauf ausgelegt, dies zu erleichtern.