Migrazione da v0.2.x
Le versioni v1.x e v2.x sono ampiamente compatibili a livello di API con la v0.2.x, ma ci sono alcune modifiche che causano incompatibilità. La migrazione alla v2.x è quasi identica, quindi le note seguenti si applicano principalmente a entrambe.
Se stai eseguendo la migrazione, considera l'utilizzo dello strumento mithril-codemods per automatizzare le migrazioni più semplici.
m.prop
rimosso
Nella v2.x, m.prop()
è stato trasformato in una più potente micro-libreria di stream, ma è stato rimosso dal core. Puoi leggere come utilizzare il modulo Streams opzionale nella documentazione.
v0.2.x
var m = require('mithril');
var num = m.prop(1);
v2.x
var m = require('mithril');
var prop = require('mithril/stream');
var num = prop(1);
var doubled = num.map(function (n) {
return n * 2;
});
m.component
rimosso
Nella v0.2.x i componenti potevano essere creati usando sia m(Component)
che m.component(Component)
. La v2.x supporta solo m(Component)
.
v0.2.x
// Questi sono equivalenti
m.component(Component);
m(Component);
v2.x
m(Component);
m.withAttr
rimosso
Nella v0.2.x i listener di eventi potevano usare oninput: m.withAttr("value", func)
e simili. Nella v2.x, leggi i valori direttamente dal target dell'evento. Funzionava bene con m.prop
, ma, essendo quest'ultimo stato rimosso a favore di una soluzione esterna al core e non avendo la v1.x visto un utilizzo idiomatico e diffuso degli stream, m.withAttr
ha perso gran parte della sua utilità.
v0.2.x
var value = m.prop('');
// 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;
},
});
m.version
rimosso
Era poco utile in generale e puoi sempre aggiungerlo di nuovo tu stesso. È preferibile utilizzare il feature detection per sapere quali funzionalità sono disponibili e l'API v2.x è progettata per consentirlo meglio.
Funzione config
Nella v0.2.x Mithril.js forniva un singolo metodo del ciclo di vita, config
. La v2.x offre un controllo più granulare sul ciclo di vita di un vnode (nodo virtuale).
v0.2.x
m('div', {
config: function (element, isInitialized) {
// viene eseguito ad ogni ridisegno
// isInitialized è un booleano che indica se il nodo è stato aggiunto al DOM
},
});
v2.x
Ulteriori informazioni su questi nuovi metodi sono disponibili in lifecycle-methods.md.
m('div', {
// Chiamato prima che il nodo DOM venga creato
oninit: function (vnode) {
/*...*/
},
// Chiamato dopo che il nodo DOM è stato creato
oncreate: function (vnode) {
/*...*/
},
// Chiamato prima che il nodo venga aggiornato, restituisce false per annullare
onbeforeupdate: function (vnode, old) {
/*...*/
},
// Chiamato dopo che il nodo è stato aggiornato
onupdate: function (vnode) {
/*...*/
},
// Chiamato prima che il nodo venga rimosso, restituisce una Promise che si risolve quando
// è pronto per la rimozione del nodo dal DOM
onbeforeremove: function (vnode) {
/*...*/
},
// Chiamato prima che il nodo venga rimosso, ma dopo che onbeforeremove chiama done()
onremove: function (vnode) {
/*...*/
},
});
Se disponibile, è possibile accedere all'elemento DOM del vnode in vnode.dom
.
Modifiche nel comportamento di ridisegno
Il motore di rendering di Mithril.js funziona ancora sulla base di ridisegni globali semi-automatici, ma alcune API e comportamenti differiscono:
Niente più blocchi di ridisegno
Nella v0.2.x, Mithril.js consentiva "blocchi di ridisegno" che impedivano temporaneamente l'esecuzione della logica di disegno: per impostazione predefinita, m.request
bloccava il ciclo di disegno all'esecuzione e sbloccava quando tutte le richieste in sospeso erano state risolte - lo stesso comportamento poteva essere invocato manualmente usando m.startComputation()
e m.endComputation()
. Queste API e il comportamento associato sono stati rimossi nella v2.x senza sostituzione. Bloccare il ridisegno può causare UI difettose: non si dovrebbe permettere che i problemi di una parte dell'applicazione impediscano ad altre parti della view di aggiornarsi e riflettere i cambiamenti.
Annullamento del ridisegno dai gestori di eventi
m.mount()
e m.route()
ridisegnano ancora automaticamente dopo l'esecuzione di un gestore di eventi DOM. Per annullare questi ridisegni all'interno dei gestori di eventi, ora è necessario impostare la proprietà redraw
dell'oggetto evento passato a false
.
v0.2.x
m('div', {
onclick: function (e) {
m.redraw.strategy('none');
},
});
v2.x
m('div', {
onclick: function (e) {
e.redraw = false;
},
});
Ridisegno sincrono modificato
Nella v0.2.x era possibile forzare Mithril.js a ridisegnare immediatamente passando un valore truthy a m.redraw()
. Nella v2.x, per maggiore chiarezza, questa funzionalità è stata suddivisa in due metodi distinti.
v0.2.x
m.redraw(true); // ridisegna immediatamente e in modo sincrono
v2.x
m.redraw(); // pianifica un ridisegno al successivo tick di requestAnimationFrame
m.redraw.sync(); // invoca un ridisegno immediatamente e attende che venga completato
m.startComputation
/m.endComputation
rimosso
Sono considerati anti-pattern e presentano diversi casi problematici, quindi sono stati rimossi senza sostituzione nella v2.x.
Funzione controller
del componente
Nella v2.x, non c'è più la proprietà controller
nei componenti - usa invece oninit
.
v0.2.x
m.mount(document.body, {
controller: function () {
var ctrl = this;
ctrl.fooga = 1;
},
view: function (ctrl) {
return m('p', ctrl.fooga);
},
});
v2.x
m.mount(document.body, {
oninit: function (vnode) {
vnode.state.fooga = 1;
},
view: function (vnode) {
return m('p', vnode.state.fooga);
},
});
// OPPURE
m.mount(document.body, {
// `this` è legato a vnode.state per impostazione predefinita
oninit: function (vnode) {
this.fooga = 1;
},
view: function (vnode) {
return m('p', this.fooga);
},
});
Argomenti del componente
Nella v2.x, gli argomenti di un componente devono essere un oggetto; i valori semplici come String
/Number
/Boolean
saranno considerati figli di tipo testo. Si accede agli argomenti all'interno del componente leggendoli dall'oggetto vnode.attrs
.
v0.2.x
var Component = {
controller: function (options) {
// options.fooga === 1
},
view: function (ctrl, options) {
// options.fooga === 1
},
};
m('div', m.component(Component, { fooga: 1 }));
v2.x
var Component = {
oninit: function (vnode) {
// vnode.attrs.fooga === 1
},
view: function (vnode) {
// vnode.attrs.fooga === 1
},
};
m('div', m(Component, { fooga: 1 }));
Elementi figli vnode del componente
Nella v2.x, gli elementi figli vnode del componente sono passati tramite vnode.children
come un array risolto di elementi figli; tuttavia, come nella v0.2.x, i singoli elementi figli non sono normalizzati, né l'array di elementi figli è appiattito.
v0.2.x
var Component = {
controller: function (value, renderProp) {
// value === "value"
// typeof renderProp === "function"
},
view: function (ctrl, value, renderProp) {
// value === "value"
// typeof renderProp === "function"
},
};
m(
'div',
m.component(Component, 'value', function (key) {
return 'child';
})
);
v2.x
var Component = {
oninit: function (vnode) {
// vnode.children[0] === "value"
// typeof vnode.children[1] === "function"
},
view: function (vnode) {
// vnode.children[0] === "value"
// typeof vnode.children[1] === "function"
},
};
m(
'div',
m(Component, 'value', function (key) {
return 'child';
})
);
Elementi figli vnode DOM
Nella v0.2.x, gli elementi figli dei nodi DOM erano rappresentati letteralmente senza alcuna normalizzazione, a parte l'utilizzo diretto degli elementi figli se è presente un solo elemento figlio array. Restituiva una struttura simile a questa.
m("div", "value", ["nested"])
// Diventa:
{
tag: "div",
attrs: {},
children: [
"value",
["nested"],
]
}
Nella v2.x, gli elementi figli dei vnode DOM sono normalizzati in oggetti con una struttura coerente.
m("div", "value", ["nested"])
// Diventa approssimativamente:
{
tag: "div",
attrs: null,
children: [
{tag: "#", children: "value"},
{tag: "[", children: [
{tag: "#", children: "nested"},
]},
]
}
Se è presente un solo elemento figlio testuale su un vnode DOM, imposta invece text
su quel valore.
m("div", "value")
// Diventa approssimativamente:
{
tag: "div",
attrs: null,
text: "",
children: undefined,
}
Vedi la documentazione sui vnode per maggiori dettagli sulla struttura vnode v2.x e su come le cose sono normalizzate.
La maggior parte delle proprietà vnode v2.x qui sono omesse per brevità.
Key
Nella v0.2.x, potevi mescolare liberamente vnode con key e senza key.
Nella v2.x, gli elenchi di elementi figli, sia di frammenti che di elementi, devono essere omogenei: tutti con key o tutti senza key. Anche i "buchi" (undefined/null) sono considerati senza key ai fini di questo controllo: non vengono più ignorati.
Se hai bisogno di una soluzione alternativa, usa l'idioma di un frammento contenente un singolo vnode, come [m("div", {key: whatever})]
.
Parametri view()
Nella v0.2.x alle funzioni view viene passato un riferimento all'istanza controller
e (facoltativamente) qualsiasi opzione passata al componente. Nella v2.x viene passato esclusivamente il vnode
, proprio come accade per la funzione controller
.
v0.2.x
m.mount(document.body, {
controller: function () {},
view: function (ctrl, options) {
// ...
},
});
v2.x
m.mount(document.body, {
oninit: function (vnode) {
// ...
},
view: function (vnode) {
// Usa vnode.state invece di ctrl
// Usa vnode.attrs invece di options
},
});
Passaggio di componenti a m()
Nella v0.2.x potevi passare i componenti come secondo argomento di m()
senza alcun wrapping richiesto. Per garantire coerenza nella v2.x, devono sempre essere inclusi in una chiamata a m()
.
v0.2.x
m('div', Component);
v2.x
m('div', m(Component));
Passaggio di vnode a m.mount()
e m.route()
Nella v0.2.x, m.mount(element, component)
tollerava vnode come secondi argomenti invece di componenti (anche se non era documentato). Allo stesso modo, m.route(element, defaultRoute, routes)
accettava vnode come valori nell'oggetto routes
.
Nella v2.x, in entrambi i casi sono richiesti componenti.
v0.2.x
m.mount(element, m('i', 'hello'));
m.mount(element, m(Component, attrs));
m.route(element, '/', {
'/': m('b', 'bye'),
});
v2.x
m.mount(element, {
view: function () {
return m('i', 'hello');
},
});
m.mount(element, {
view: function () {
return m(Component, attrs);
},
});
m.route(element, '/', {
'/': {
view: function () {
return m('b', 'bye');
},
},
});
m.route.mode
Nella v0.2.x, la modalità di routing si impostava assegnando una stringa tra "pathname"
, "hash"
o "search"
a m.route.mode
. Nella v1.x è stata sostituita con m.route.prefix = prefix
, dove prefix
può essere un qualsiasi prefisso. Se inizia con #
, funziona in modalità "hash", ?
per la modalità "search" e qualsiasi altro carattere (o la stringa vuota) per la modalità "pathname". Supporta anche combinazioni delle precedenti come m.route.prefix = "/path/#!"
o ?#
.
L'impostazione predefinita è stata modificata per utilizzare un prefisso #!
(hashbang) invece di solo #
. Pertanto, se stavi utilizzando il comportamento predefinito e desideri conservare gli URL esistenti, specifica m.route.prefix = "#"
prima di inizializzare le route.
v0.2.x
m.route.mode = 'hash';
m.route.mode = 'pathname';
m.route.mode = 'search';
v2.x
// Equivalenti diretti
m.route.prefix = '#';
m.route.prefix = '';
m.route.prefix = '?';
m.route()
e tag di ancoraggio
Ora, per la gestione dei link con routing, si utilizza un componente speciale integrato, invece di un attributo. Se lo stavi usando su elementi come <button>
, puoi specificare il nome del tag usando l'attributo selector: "button"
.
v0.2.x
// Cliccando su questo link, verrà caricata la route "/path" invece di navigare
m('a', {
href: '/path',
config: m.route,
});
v2.x
// Cliccando su questo link, verrà caricata la route "/path" invece di navigare
m(m.route.Link, {
href: '/path',
});
Template di percorso
Nella v1.x, esistevano tre sintassi separate per i template di percorso che, pur essendo simili, avevano due sintassi progettate separatamente e tre implementazioni diverse. Era definita in modo ad hoc e i parametri non venivano generalmente escaped. Ora, tutto viene codificato se è :key
, raw se è :key...
. Se le cose vengono codificate in modo imprevisto, usa :key...
. È molto semplice.
Ecco come influisce su ciascun metodo:
URL di m.request
Nella v2.x, i componenti del percorso vengono escaped automaticamente quando vengono interpolati e leggono i loro valori da params
. Nella v0.2.x, m.request({url: "/user/:name/photos/:id", data: {name: "a/b", id: "c/d"}})
invierebbe la sua richiesta con l'URL impostato su /user/a%2Fb/photos/c/d
. Nella v2.x, il corrispondente m.request({url: "/user/:name/photos/:id", params: {name: "a/b", id: "c/d"}})
invierebbe la sua richiesta a /user/a%2Fb/photos/c%2Fd
. Se si desidera deliberatamente interpolare una key senza escape, utilizzare invece :key...
.
Le interpolazioni nelle query string inline, come in /api/search?q=:query
, non vengono eseguite nella v2.x. Passa invece quelle tramite params
con i nomi delle key appropriati, senza specificarlo nella query string.
Nota che questo vale anche per m.jsonp
. Quando si esegue la migrazione da m.request
+ dataType: "jsonp"
a m.jsonp
, è necessario tenerne conto.
Percorsi di m.route(route, params, shouldReplaceHistoryEntry)
Questi ora consentono le interpolazioni e funzionano in modo identico a quello di m.request
.
Pattern di route di m.route
Nella v1.x, le key del percorso nella forma :key...
restituiscono il loro URL decodificato, mentre nella v2.x restituiscono l'URL raw.
In precedenza, elementi come :key.md
venivano erroneamente accettati, con il valore del parametro risultante impostato su keymd: "..."
. Questo non è più il caso: .md
ora fa parte del pattern, non del nome.
Lettura/scrittura della route corrente
Nella v0.2.x, tutte le interazioni con la route corrente avvenivano tramite m.route()
. Nella v2.x questo è stato suddiviso in due funzioni.
v0.2.x
// Ottenere la route corrente
m.route();
// Impostare una nuova route
m.route('/other/route');
v2.x
// Ottenere la route corrente
m.route.get();
// Impostare una nuova route
m.route.set('/other/route');
Accesso ai parametri della route
Nella v0.2.x, la lettura dei parametri della route era gestita completamente tramite m.route.param()
. Questa API è ancora disponibile nella v2.x e, inoltre, tutti i parametri della route vengono passati come proprietà nell'oggetto attrs
sul vnode (nodo virtuale).
v0.2.x
m.route(document.body, '/booga', {
'/:attr': {
controller: function () {
m.route.param('attr'); // "booga"
},
view: function () {
m.route.param('attr'); // "booga"
},
},
});
v2.x
m.route(document.body, '/booga', {
'/:attr': {
oninit: function (vnode) {
vnode.attrs.attr; // "booga"
m.route.param('attr'); // "booga"
},
view: function (vnode) {
vnode.attrs.attr; // "booga"
m.route.param('attr'); // "booga"
},
},
});
Costruzione/parsing di query string
La v0.2.x utilizzava metodi associati a m.route
, m.route.buildQueryString()
e m.route.parseQueryString()
. Nella v2.x questi sono stati separati e spostati alla root m
.
v0.2.x
var qs = m.route.buildQueryString({ a: 1 });
var obj = m.route.parseQueryString('a=1');
v2.x
var qs = m.buildQueryString({ a: 1 });
var obj = m.parseQueryString('a=1');
Inoltre, nella v2.x, {key: undefined}
viene serializzato come key=undefined
da m.buildQueryString
e dai metodi che lo utilizzano come m.request
. Nella v0.2.x, la key veniva omessa e questo è stato riportato a m.request
. Se in precedenza ti affidavi a questo, modifica il tuo codice per omettere completamente le key dall'oggetto. Potresti utilizzare una semplice utility per rimuovere tutte le key da un oggetto i cui valori sono undefined
, qualora non fosse possibile farlo facilmente e si volesse conservare il comportamento della v0.2.x.
// Usala quando devi omettere i parametri `undefined` da un oggetto.
function omitUndefineds(object) {
var result = {};
for (var key in object) {
if ({}.hasOwnProperty.call(object, key)) {
var value = object[key];
if (Array.isArray(value)) {
result[key] = value.map(omitUndefineds);
} else if (value != null && typeof value === 'object') {
result[key] = omitUndefineds(value);
} else if (value !== undefined) {
result[key] = value;
}
}
}
return result;
}
Prevenzione dello smontaggio
Non è più possibile impedire lo smontaggio tramite e.preventDefault()
all'interno di onunload
. Invece, dovresti chiamare esplicitamente m.route.set
quando vengono soddisfatte le condizioni previste.
v0.2.x
var Component = {
controller: function () {
this.onunload = function (e) {
if (condition) e.preventDefault();
};
},
view: function () {
return m('a[href=/]', { config: m.route });
},
};
v2.x
var Component = {
view: function () {
return m('a', {
onclick: function () {
if (!condition) m.route.set('/');
},
});
},
};
Esecuzione di codice alla rimozione del componente
Quando vengono rimossi, i componenti non chiamano più this.onunload
. Ora utilizzano l'hook del ciclo di vita standardizzato onremove
.
v0.2.x
var Component = {
controller: function () {
this.onunload = function (e) {
// ...
};
},
view: function () {
// ...
},
};
v2.x
var Component = {
onremove: function() {
// ...
}
view: function() {
// ...
}
}
m.request
Le Promise restituite da m.request non sono più getter-setter m.prop
. Inoltre, initialValue
, unwrapSuccess
e unwrapError
non sono più opzioni supportate.
Inoltre, le richieste non hanno più la semantica m.startComputation
/m.endComputation
. Invece, i redraw vengono sempre attivati quando una catena di Promise di richiesta viene completata (a meno che non sia impostato background: true
).
Il parametro data
è stato ora suddiviso in params
, parametri di query interpolati nell'URL e aggiunti alla richiesta, e body
(corpo), il body da inviare nell'XHR sottostante.
Nella v0.2.x, useresti dataType: "jsonp"
per avviare una richiesta JSONP. Nella v2.x, ora usi m.jsonp
, che ha sostanzialmente la stessa API di m.request
senza le parti relative a XHR.
v0.2.x
var data = m.request({
method: 'GET',
url: 'https://api.github.com/',
initialValue: [],
});
setTimeout(function () {
console.log(data());
}, 1000);
m.request({
method: 'POST',
url: 'https://api.github.com/',
data: someJson,
});
v2.x
var data = [];
m.request({
method: 'GET',
url: 'https://api.github.com/',
}).then(function (responseBody) {
data = responseBody;
});
setTimeout(function () {
console.log(data); // note: not a getter-setter
}, 1000);
m.request({
method: 'POST',
url: 'https://api.github.com/',
body: someJson,
});
// OR
var data = [];
m.request('https://api.github.com/').then(function (responseBody) {
data = responseBody;
});
setTimeout(function () {
console.log(data); // note: not a getter-setter
}, 1000);
m.request('https://api.github.com/', {
method: 'POST',
body: someJson,
});
Inoltre, se l'opzione extract
viene passata a m.request
, il valore di ritorno della funzione fornita verrà utilizzato direttamente per risolvere la Promise di richiesta e la callback deserialize
viene ignorata.
Header di m.request
Nella v0.2.x, Mithril.js non impostava alcuna intestazione sulle richieste per impostazione predefinita. Ora, imposta fino a 2 header:
Content-Type: application/json; charset=utf-8
per le richieste con body JSON che sono!= null
Accept: application/json, text/*
per le richieste che si aspettano risposte JSON
Il primo dei due header, Content-Type
, attiverà un prefetch CORS in quanto non è un header di richiesta CORS-safelisted a causa del tipo di contenuto specificato; ciò potrebbe introdurre nuovi errori a seconda di come è configurato CORS sul server. Se riscontri problemi con questo, potrebbe essere necessario sovrascrivere l'header in questione 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 prefetch CORS sono application/x-www-form-urlencoded
, multipart/form-data
e text/plain
. Non consente nient'altro e vieta intenzionalmente JSON.
m.deferred
rimosso
La v0.2.x utilizzava il proprio oggetto di contratto asincrono personalizzato, esposto come m.deferred
, che veniva utilizzato come base per m.request
. La v2.x utilizza invece le Promise e implementa un polyfill in ambienti non supportati. Nei casi in cui usavi m.deferred
, dovresti invece utilizzare le Promise.
v0.2.x
var greetAsync = function () {
var deferred = m.deferred();
setTimeout(function () {
deferred.resolve('hello');
}, 1000);
return deferred.promise;
};
greetAsync()
.then(function (value) {
return value + ' world';
})
.then(function (value) {
console.log(value);
}); //logs "hello world" after 1 second
v2.x
var greetAsync = function () {
return new Promise(function (resolve) {
setTimeout(function () {
resolve('hello');
}, 1000);
});
};
greetAsync()
.then(function (value) {
return value + ' world';
})
.then(function (value) {
console.log(value);
}); //logs "hello world" after 1 second
m.sync
rimosso
Poiché la v2.x utilizza Promise conformi agli standard, m.sync
è ridondante. Usa invece Promise.all
.
v0.2.x
m.sync([
m.request({ method: 'GET', url: 'https://api.github.com/users/lhorie' }),
m.request({
method: 'GET',
url: 'https://api.github.com/users/dead-claudia',
}),
]).then(function (users) {
console.log('Contributors:', users[0].name, 'and', users[1].name);
});
v2.x
Promise.all([
m.request({ method: 'GET', url: 'https://api.github.com/users/lhorie' }),
m.request({
method: 'GET',
url: 'https://api.github.com/users/dead-claudia',
}),
]).then(function (users) {
console.log('Contributors:', users[0].name, 'and', users[1].name);
});
Namespace xlink
richiesto
Nella v0.2.x, il namespace xlink
era l'unico namespace di attributo supportato ed era supportato tramite un comportamento di special casing. Ora il parsing del namespace è completamente supportato e gli attributi con namespace devono dichiarare esplicitamente il proprio namespace.
v0.2.x
m(
'svg',
// l'attributo `href` è namespaced automaticamente
m("image[href='image.gif']")
);
v2.x
m(
'svg',
// Namespace specificato dall'utente sull'attributo `href`
m("image[xlink:href='image.gif']")
);
Array nidificati nelle view
Ora, gli array rappresentano frammenti, che sono strutturalmente significativi nel DOM virtuale della v2.x. Mentre gli array nidificati nella v0.2.x verrebbero appiattiti in un unico elenco continuo di nodi virtuali ai fini del diffing, la v2.x preserva la struttura dell'array: i figli di un dato array non sono considerati fratelli di quelli di array adiacenti.
Controlli di uguaglianza vnode
Se un vnode è strettamente uguale al vnode che occupa il suo posto nell'ultimo draw (rendering), la v2.x salterà quella parte dell'albero senza verificare la presenza di mutazioni o attivare alcun metodo del ciclo di vita nel sottoalbero. La documentazione del componente contiene maggiori dettagli su questo problema.