Migrazione da v1.x
La v2.x è quasi completamente API-compatibile con la v1.x, ma ci sono alcune modifiche che introducono incompatibilità.
Assegnazione a vnode.state
Nella v1.x, era possibile manipolare vnode.state
e assegnargli qualsiasi valore. Nella v2.x, verrà generato un errore se si tenta di modificarlo. La migrazione può variare, ma nella maggior parte dei casi è sufficiente sostituire i riferimenti a vnode.state
con vnode.state.foo
, scegliendo un nome appropriato per foo
(ad esempio count
se rappresenta il valore corrente di un contatore).
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++;
},
},
'+'
),
]);
},
};
Quando la v1.0 è stata rilasciata, i componenti classe e closure non esistevano, quindi si utilizzava semplicemente ciò che era presente in vnode.tag
. Questo dettaglio implementativo ha reso possibile tale comportamento, e alcuni sviluppatori hanno iniziato a farvi affidamento. Era anche implicitamente suggerito in alcuni punti della documentazione. Ora, le cose sono cambiate, e questo semplifica la gestione dal punto di vista dell'implementazione, dato che c'è un solo riferimento allo stato, invece di due.
Modifiche alle ancore di routing
Nella v1.x, si utilizzavano oncreate: m.route.link
e, se il link poteva cambiare, anche onupdate: m.route.link
, come hook del ciclo di vita sul vnode che rappresentava il link navigabile. Nella v2.x, si utilizza invece il componente m.route.Link
. Il selettore può essere definito tramite un attributo selector:
se si utilizza qualcosa di diverso da m("a", ...)
le opzioni possono essere definite tramite options:
, è possibile disabilitarlo tramite disabled:
, e altri attributi possono essere specificati inline, incluso href:
(obbligatorio). L'attributo selector:
stesso può contenere qualsiasi selettore valido come primo argomento per m
, e gli attributi [href=...]
e [disabled]
possono essere specificati nel selettore così come nelle normali opzioni.
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]',
});
Modifiche agli errori di m.request
Nella v1.x, m.request
interpretava gli errori delle chiamate JSON e assegnava le proprietà dell'oggetto analizzato risultante alla risposta. Se si riceveva una risposta con stato 403 e un corpo di {"code": "backoff", "timeout": 1000}
, all'errore venivano assegnate due proprietà extra: err.code = "backoff"
e err.timeout = 1000
.
Nella v2.x, la risposta viene assegnata a una proprietà response
del risultato, e una proprietà code
contiene il codice di stato risultante. Quindi, se si riceveva una risposta con stato 403 e un corpo di {"code": "backoff", "timeout": 1000}
, l'errore avrebbe assegnato due proprietà: err.response = {code: "backoff", timeout: 1000}
e err.code = 403
.
m.withAttr
rimosso
Nella v1.x, i listener di eventi potevano utilizzare oninput: m.withAttr("value", func)
e simili. Nella v2.x, si leggono i valori direttamente dal target dell'evento stesso. Si integrava bene con gli stream, ma poiché l'utilizzo di m.withAttr("value", stream)
non era diffuso quanto m.withAttr("value", prop)
, m.withAttr
ha perso gran parte della sua utilità ed è stato quindi rimosso.
v1.x
var value = '';
// Nella tua view
m('input[type=text]', {
value: value(),
oninput: m.withAttr('value', function (v) {
value = v;
}),
});
// OPPURE
var value = m.stream('');
// Nella tua view
m('input[type=text]', {
value: value(),
oninput: m.withAttr('value', value),
});
v2.x
var value = '';
// Nella tua view
m('input[type=text]', {
value: value,
oninput: function (ev) {
value = ev.target.value;
},
});
// OPPURE
var value = m.stream('');
// Nella tua view
m('input[type=text]', {
value: value(),
oninput: function (ev) {
value(ev.target.value);
},
});
m.route.prefix
Nella v1.x, m.route.prefix
era una funzione invocata tramite m.route.prefix(prefix)
. Ora è una proprietà che si imposta tramite m.route.prefix = prefix
v1.x
m.route.prefix('/root');
v2.x
m.route.prefix = '/root';
Parametri e body di m.request
/m.jsonp
data
e useBody
sono stati riorganizzati in params
, parametri di query interpolati nell'URL e aggiunti alla richiesta, e body
, il corpo da inviare nell'XHR sottostante. Questo offre un controllo maggiore sulla richiesta effettiva e consente sia di interpolare i parametri di query nelle richieste POST
sia di creare richieste GET
con un body.
m.jsonp
, non avendo un "body" significativo, usa solo params
, quindi rinominare data
in params
è sufficiente per quel metodo.
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,
});
Template di percorso
Nella v1.x, c'erano tre sintassi separate per i template di percorso che, sebbene fossero simili, avevano 2 sintassi progettate separatamente e 3 implementazioni diverse. Era definito in modo piuttosto ad-hoc, e i parametri non erano generalmente escaped. Ora, tutto viene codificato se è :key
, lasciato grezzo se è :key...
. Se si verifica una codifica inattesa, utilizzare :path...
. È così semplice.
Concretamente, ecco come influisce su ciascun metodo:
URL di m.request
e m.jsonp
, percorsi di m.route.set
I componenti del percorso nella v2.x sono codificati automaticamente quando interpolati. Supponiamo di invocare m.route.set("/user/:name/photos/:id", {name: user.name, id: user.id})
. In precedenza, se user
era {name: "a/b", id: "c/d"}
, questo avrebbe impostato la route a /user/a%2Fb/photos/c/d
, ma ora la imposterà a /user/a%2Fb/photos/c%2Fd
. Se si desidera deliberatamente interpolare una key non codificata, usare invece :key...
.
Nella v2.x, i nomi delle chiavi non possono contenere istanze di .
o -
. Nella v1.x, potevano contenere qualsiasi cosa tranne /
.
Le interpolazioni nelle stringhe di query inline, come in /api/search?q=:query
, non vengono eseguite nella v2.x. Passare invece quelle tramite params
con nomi di key appropriati, senza specificarlo nella query string.
Pattern di route di m.route
Le key di percorso della forma :key...
restituiscono il loro URL decodificato nella v1.x, ma restituiscono l'URL raw nella v2.x.
In precedenza, costrutti come :key.md
erano erroneamente accettati, con il valore del parametro risultante impostato su keymd: "..."
. Questo non è più il caso - .md
fa ora parte del pattern, non del nome.
Ordine di chiamata del ciclo di vita
Nella v1.x, gli hook del ciclo di vita degli attributi sui vnode dei componenti venivano chiamati prima degli hook del ciclo di vita del componente stesso in tutti i casi. Nella v2.x, questo è il caso solo per onbeforeupdate
. Pertanto, potrebbe essere necessario modificare il codice di conseguenza.
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
Sincronicità di m.redraw
m.redraw()
nella v2.x è sempre asincrono. È possibile richiedere specificamente un redraw sincrono tramite m.redraw.sync()
a condizione che non sia attualmente in corso alcun redraw.
Precedenza degli attributi del selettore
Nella v1.x, gli attributi del selettore avevano la precedenza sugli attributi specificati nell'oggetto degli attributi. Ad esempio, m("[a=b]", {a: "c"}).attrs
restituiva {a: "b"}
.
Nella v2.x, gli attributi specificati nell'oggetto degli attributi hanno la precedenza sugli attributi del selettore. Ad esempio, m("[a=b]", {a: "c"}).attrs
restituisce {a: "c"}
.
Si noti che questo comportamento ripristina tecnicamente quello della v0.2.x.
Normalizzazione dei children
Nella v1.x, i children dei vnode dei componenti venivano normalizzati come altri vnode. Nella v2.x, questo non è più il caso e sarà necessario tenerne conto. Questo non influisce sulla normalizzazione eseguita sul render.
Header di m.request
Nella v1.x, Mithril.js impostava questi due header su tutte le richieste non-GET
, ma solo quando useBody
era impostato su true
(il valore predefinito) e le altre condizioni elencate erano valide:
Content-Type: application/json; charset=utf-8
per le richieste con body JSONAccept: application/json, text/*
per le richieste che si aspettano risposte JSON
Nella v2.x, Mithril.js imposta il primo header per tutte le richieste con body JSON non nulli (!= null
) e lo omette altrimenti. Questo avviene indipendentemente dal metodo HTTP utilizzato, incluse le richieste GET
.
Il primo dei due header, Content-Type
, attiverà un preflight CORS in quanto non è un header di richiesta CORS-safelisted a causa del tipo di contenuto specificato, e questo potrebbe introdurre nuovi errori a seconda di come è configurato CORS sul tuo server. In caso di problemi, è possibile sovrascrivere l'header passando headers: {"Content-Type": "text/plain"}
. (L'header Accept
non attiva nulla, quindi non è necessario sovrascriverlo.)
Gli unici tipi di contenuto che la specifica Fetch consente di evitare i controlli di preflight CORS sono application/x-www-form-urlencoded
, multipart/form-data
e text/plain
. Non consente nient'altro, e intenzionalmente non consente JSON.
Parametri di query nelle stringhe hash nelle route
Nella v1.x, era possibile specificare i parametri di query per le route sia nella query string che nella stringa hash, quindi m.route.set("/route?foo=1&bar=2")
, m.route.set("/route?foo=1#bar=2")
e m.route.set("/route#foo=1&bar=2")
erano tutti equivalenti e gli attributi estratti da essi sarebbero stati {foo: "1", bar: "2"}
.
Nella v2.x, il contenuto delle stringhe hash viene ignorato ma rimane preservato. Quindi gli attributi estratti da ciascuno sarebbero questi:
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")
→{}
Questo perché URL come https://example.com/#!/route#key
sono tecnicamente non validi secondo la specifica URL e lo erano anche secondo la RFC precedente. La loro validità è dovuta a una peculiarità della specifica HTML.
O in breve, evitare di usare URL non validi!
Key
Nella v1.x, era possibile mescolare liberamente vnode con key e senza key. Se il primo nodo ha una key, viene eseguito un diff con key, assumendo che ogni elemento abbia una key e ignorando semplicemente i buchi mentre procede. Altrimenti, viene eseguito un diff iterativo, e se un nodo ha una key, verrebbe controllato che non sia cambiato nello stesso momento in cui vengono controllati i tag e simili.
Nella v2.x, le liste di elementi figli, sia di frammenti che di elementi, devono essere omogenee: tutti gli elementi devono avere una chiave, oppure nessuno deve averla. Ai fini di questo controllo, anche i "buchi" (elementi mancanti) sono considerati come elementi senza chiave e non vengono più ignorati.
Se è necessario aggirare questa limitazione, usare l'idioma di un frammento contenente un singolo vnode, come [m("div", {key: whatever})]
.
m.version
rimosso
Aveva un'utilità limitata in generale ed è comunque possibile reintrodurla manualmente. È preferibile utilizzare il feature detection per sapere quali funzionalità sono disponibili, e l'API v2.x è progettata per facilitare questo approccio.