Migración desde v0.2.x
Las versiones v1.x y v2.x son en gran medida compatibles con la API de v0.2.x, pero existen algunos cambios importantes. La migración a v2.x es casi idéntica, por lo que las siguientes notas se aplican principalmente a ambas.
Si está migrando, considere usar la herramienta mithril-codemods para automatizar las migraciones más comunes.
m.prop
eliminado
En v2.x, m.prop()
se transformó en una micro-librería de streams más potente, pero ya no forma parte del núcleo de Mithril. Puede obtener más información sobre cómo usar el módulo Streams opcional en la documentación.
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
eliminado
En v0.2.x, los componentes podían crearse usando m(Component)
o m.component(Component)
. En v2.x, solo se admite m(Component)
; m.component(Component)
ya no es válido.
v0.2.x
// Estos son equivalentes
m.component(Component);
m(Component);
v2.x
m(Component);
m.withAttr
eliminado
En v0.2.x, los controladores de eventos podían usar oninput: m.withAttr("value", func)
y similares. En v2.x, acceda directamente al valor desde la propiedad target
del evento. Esto funcionaba bien con m.prop()
, pero como este se eliminó en favor de una solución externa al núcleo y v1.x no tuvo un uso idiomático amplio similar de streams, m.withAttr
perdió la mayor parte de su utilidad.
v0.2.x
var value = m.prop('');
// En tu vista
m('input[type=text]', {
value: value(),
oninput: m.withAttr('value', value),
});
v2.x
var value = '';
// En tu vista
m('input[type=text]', {
value: value,
oninput: function (ev) {
value = ev.target.value;
},
});
m.version
eliminado
En general, tenía poco uso, y siempre puede volver a añadirlo usted mismo si lo necesita. Se recomienda la detección de características para determinar qué funcionalidades están disponibles, y la API de v2.x está diseñada para facilitar esto.
Función config
En v0.2.x, Mithril.js proporcionaba un único método de ciclo de vida, config
. v2.x proporciona un control mucho más preciso sobre el ciclo de vida de un vnode.
v0.2.x
m('div', {
config: function (element, isInitialized) {
// se ejecuta en cada redibujo
// isInitialized es un booleano que indica si el nodo se ha agregado al DOM
},
});
v2.x
Puede encontrar más documentación sobre estos nuevos métodos en lifecycle-methods.md.
m('div', {
// Se llama antes de que se cree el nodo DOM
oninit: function (vnode) {
/*...*/
},
// Se llama después de que se crea el nodo DOM
oncreate: function (vnode) {
/*...*/
},
// Se llama antes de que se actualice el nodo; devuelve false para cancelar la actualización
onbeforeupdate: function (vnode, old) {
/*...*/
},
// Se llama después de que se actualiza el nodo
onupdate: function (vnode) {
/*...*/
},
// Se llama antes de que se elimine el nodo; devuelve una Promesa que se resuelve cuando
// está listo para que el nodo se elimine del DOM
onbeforeremove: function (vnode) {
/*...*/
},
// Se llama antes de que se elimine el nodo, pero después de que onbeforeremove llama a done()
onremove: function (vnode) {
/*...*/
},
});
Si está disponible, se puede acceder al elemento DOM del vnode a través de vnode.dom
.
Cambios en el comportamiento de redibujo
El motor de renderizado de Mithril.js sigue operando sobre la base de redibujos globales semiautomatizados, pero algunas API y algunos comportamientos difieren:
No más bloqueos de redibujo
En v0.2.x, Mithril.js permitía "bloqueos de redibujo" que impedían temporalmente la lógica de redibujo. Por defecto, m.request
bloqueaba el bucle de redibujo durante la ejecución y lo desbloqueaba al resolverse todas las solicitudes pendientes. El mismo comportamiento se podía invocar manualmente con m.startComputation()
y m.endComputation()
. Estas API y el comportamiento asociado se eliminaron en v2.x sin reemplazo. El bloqueo de redibujo puede provocar interfaces de usuario con errores: no se debe permitir que los problemas de una parte de la aplicación impidan que otras partes de la vista se actualicen y reflejen los cambios.
Cancelación del redibujo desde los controladores de eventos
m.mount()
y m.route()
todavía se redibujan automáticamente después de ejecutar un controlador de eventos DOM. La cancelación de estos redibujos desde los controladores de eventos ahora se realiza configurando la propiedad redraw
del objeto de evento a false
.
v0.2.x
m('div', {
onclick: function (e) {
m.redraw.strategy('none');
},
});
v2.x
m('div', {
onclick: function (e) {
e.redraw = false;
},
});
Redibujo síncrono cambiado
En v0.2.x, era posible forzar a Mithril.js a redibujar inmediatamente pasando un valor truthy a m.redraw()
. En v2.x, esta funcionalidad se dividió en dos métodos diferentes para mayor claridad.
v0.2.x
m.redraw(true); // redibuja de inmediato y de forma sincrónica
v2.x
m.redraw(); // programa un redibujo para el siguiente ciclo de requestAnimationFrame
m.redraw.sync(); // invoca un redibujo inmediatamente y espera a que se complete
m.startComputation
/m.endComputation
eliminado
Se consideran antipatrones y presentan varios casos extremos problemáticos, por lo que se eliminaron en v2.x sin reemplazo.
Función controller
del componente
En v2.x, los componentes ya no tienen la propiedad controller
; use oninit
en su lugar.
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);
},
});
// O
m.mount(document.body, {
// esto está vinculado a vnode.state por defecto
oninit: function (vnode) {
this.fooga = 1;
},
view: function (vnode) {
return m('p', this.fooga);
},
});
Argumentos del componente
En v2.x, los argumentos de un componente deben ser un objeto. Los valores simples como String
, Number
o Boolean
se tratarán como nodos hijo de texto. Se accede a los argumentos dentro del componente leyéndolos del objeto 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 }));
Children del vnode del componente
En v0.2.x, los nodos hijo del vnode del componente no se normalizaban, sino que se pasaban como argumentos adicionales y no se aplanaban. (Internamente, se devolvía un componente aplicado parcialmente que se diferenciaba según si el componente se aplicaba parcialmente). En v2.x, los nodos hijo del vnode del componente se pasan a través de vnode.children
como una matriz resuelta de nodos hijo. Al igual que en v0.2.x, los nodos hijo individuales no se normalizan ni se aplana la matriz de nodos hijo.
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';
})
);
Children del vnode DOM
En v0.2.x, los nodos hijo de los nodos DOM se representaban literalmente, sin normalización, excepto cuando solo había un único nodo hijo de tipo matriz, en cuyo caso se usaba directamente. Esto resultaba en una estructura similar a la siguiente, con las cadenas representadas literalmente.
m("div", "value", ["nested"])
// Se convierte en:
{
tag: "div",
attrs: {},
children: [
"value",
["nested"],
]
}
En v2.x, los nodos hijo de los vnodes DOM se normalizan a objetos con una estructura única y consistente.
m("div", "value", ["nested"])
// Se convierte aproximadamente en:
{
tag: "div",
attrs: null,
children: [
{tag: "#", children: "value"},
{tag: "[", children: [
{tag: "#", children: "nested"},
]},
]
}
Si solo está presente un único nodo hijo de texto en un vnode DOM, en su lugar establece text
en ese valor.
m("div", "value")
// Se convierte aproximadamente en:
{
tag: "div",
attrs: null,
text: "",
children: undefined,
}
Consulte la documentación de vnode para obtener más detalles sobre la estructura de vnode v2.x y cómo se normalizan las cosas.
La mayoría de las propiedades de vnode v2.x aquí se omiten por brevedad.
Keys
En v0.2.x, se podían mezclar libremente vnodes con y sin clave.
En v2.x, las listas de nodos hijo de fragmentos y elementos deben tener todas las claves o ninguna. Los huecos se consideran sin clave para esta verificación; ya no se ignoran.
Si necesita evitar esta restricción, use un fragmento que contenga un único vnode, como [m("div", {key: whatever})]
.
Parámetros view()
En v0.2.x, las funciones de vista recibían una referencia a la instancia controller
y, opcionalmente, cualquier opción pasada al componente. En v2.x, solo reciben el vnode
, igual que la función oninit
.
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) {
// Use vnode.state en lugar de ctrl
// Use vnode.attrs en lugar de options
},
});
Pasar componentes a m()
En v0.2.x, se podían pasar componentes como segundo argumento de m()
sin necesidad de un envoltorio. Para mantener la coherencia en v2.x, siempre deben estar envueltos en una invocación m()
.
v0.2.x
m('div', Component);
v2.x
m('div', m(Component));
Pasar vnodes a m.mount()
y m.route()
En v0.2.x, m.mount(element, component)
toleraba vnodes como segundos argumentos en lugar de componentes (aunque no estuviera documentado). De forma similar, m.route(element, defaultRoute, routes)
aceptaba vnodes como valores en el objeto routes
.
En v2.x, se requieren componentes en ambos casos.
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
En la versión 0.2.x, el modo de enrutamiento se configuraba asignando una cadena de texto ("pathname"
, "hash"
o "search"
) a m.route.mode
. En la versión 1.x, esto se reemplazó por m.route.prefix = prefix
, donde prefix
puede ser cualquier prefijo. Si el prefijo comienza con #
, funciona en modo "hash"; si comienza con ?
, funciona en modo "search"; y cualquier otro carácter (o la cadena vacía) indica el modo "pathname". También admite combinaciones de lo anterior, como m.route.prefix = "/path/#!"
o ?#
.
El valor predeterminado se cambió para usar un prefijo #!
(hashbang) en lugar de solo #
. Por lo tanto, si estaba utilizando el comportamiento predeterminado y desea conservar sus URL existentes, especifique m.route.prefix = "#"
antes de inicializar las rutas.
v0.2.x
m.route.mode = 'hash';
m.route.mode = 'pathname';
m.route.mode = 'search';
v2.x
// Equivalentes directos
m.route.prefix = '#';
m.route.prefix = '';
m.route.prefix = '?';
m.route()
y etiquetas ancla
El manejo de enlaces enrutables ahora utiliza un componente especial incorporado en lugar de un atributo. Si estaba usando esto en elementos <button>
y similares, puede especificar el nombre de la etiqueta usando el atributo selector: "button"
.
v0.2.x
// Al hacer clic en este enlace, se cargará la ruta '/path' en lugar de navegar
m('a', {
href: '/path',
config: m.route,
});
v2.x
// Al hacer clic en este enlace, se cargará la ruta '/path' en lugar de navegar
m(m.route.Link, {
href: '/path',
});
Plantillas de ruta (Path templates)
En la versión 1.x, existían tres sintaxis de plantilla de ruta separadas que, aunque similares, tenían dos sintaxis diseñadas por separado y tres implementaciones diferentes. Se definían de una manera ad hoc y los parámetros generalmente no se escapaban. Ahora, todo se codifica si es :key
, y no se codifica si es :key...
. Si las cosas se codifican inesperadamente, use :path...
. Es así de simple.
Concretamente, así es como afecta a cada método:
URLs de m.request
Los componentes de ruta en la versión 2.x se escapan automáticamente cuando se interpolan y leen sus valores de params
. En la versión 0.2.x, m.request({url: "/user/:name/photos/:id", data: {name: "a/b", id: "c/d"}})
enviaría su solicitud con la URL establecida en /user/a%2Fb/photos/c/d
. En la versión 2.x, el equivalente m.request({url: "/user/:name/photos/:id", params: {name: "a/b", id: "c/d"}})
enviaría su solicitud a /user/a%2Fb/photos/c%2Fd
. Si deliberadamente quiere interpolar una clave sin escapar, use :key...
en su lugar.
Las interpolaciones en cadenas de consulta en línea, como en /api/search?q=:query
, no se realizan en la versión 2.x. Páselos a través de params
con los nombres de clave apropiados en su lugar, sin especificarlos en la cadena de consulta.
Tenga en cuenta que esto también se aplica a m.jsonp
. Al migrar de m.request
+ dataType: "jsonp"
a m.jsonp
, también debe tener esto en cuenta.
Rutas de m.route(route, params, shouldReplaceHistoryEntry)
Estas ahora permiten interpolaciones y funcionan de forma idéntica a las de m.request
.
Patrones de ruta de m.route
Las claves de ruta de la forma :key...
devuelven su URL decodificada en la versión 1.x, pero devuelven la URL sin formato en la versión 2.x.
Anteriormente, cosas como :key.md
se aceptaban erróneamente, con el valor del parámetro resultante establecido en keymd: "..."
. Esto ya no es el caso: el .md
ahora es parte del patrón, no del nombre.
Leer/escribir la ruta actual
En la versión 0.2.x, toda la interacción con la ruta actual se realizaba a través de m.route()
. En la versión 2.x, esto se ha dividido en dos funciones.
v0.2.x
// Obteniendo la ruta actual
m.route();
// Estableciendo una nueva ruta
m.route('/other/route');
v2.x
// Obteniendo la ruta actual
m.route.get();
// Estableciendo una nueva ruta
m.route.set('/other/route');
Acceder a los parámetros de la ruta
En la versión 0.2.x, la lectura de los parámetros de la ruta se manejaba por completo a través de m.route.param()
. Esta API todavía está disponible en la versión 2.x, y además, cualquier parámetro de ruta se pasa como propiedades en el objeto attrs
en el vnode.
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"
},
},
});
Construir/Analizar cadenas de consulta (query strings)
La versión 0.2.x utilizaba métodos que dependían de m.route
, m.route.buildQueryString()
y m.route.parseQueryString()
. En la versión 2.x, estos se han dividido y movido a la raíz 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');
Además, en la versión 2.x, {key: undefined}
se serializa como key=undefined
mediante m.buildQueryString
y los métodos que lo usan como m.request
. En la versión 0.2.x, la clave se omitió y esto se trasladó a m.request
. Si antes dependía de esto, cambie su código para omitir las claves del objeto por completo. Podría valer la pena usar una utilidad simple para eliminar todas las claves de un objeto cuyos valores son undefined
si no puede hacerlo fácilmente y necesita conservar el comportamiento de la versión 0.2.x.
// Llama a esta función cuando necesites omitir parámetros `undefined` de un objeto.
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;
}
Prevenir el desmontaje
Ya no es posible evitar el desmontaje a través de e.preventDefault()
de onunload
. En su lugar, debe llamar explícitamente a m.route.set
cuando se cumplan las condiciones esperadas.
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('/');
},
});
},
};
Ejecutar código al eliminar el componente
Los componentes ya no invocan this.onunload
cuando se eliminan. Ahora utilizan el hook de ciclo de vida estandarizado onremove
.
v0.2.x
var Component = {
controller: function () {
this.onunload = function (e) {
// ...
};
},
view: function () {
// ...
},
};
v2.x
var Component = {
onremove: function() {
// ...
}
view: function() {
// ...
}
}
m.request
Las promesas devueltas por m.request ya no son getter-setters m.prop
. Además, initialValue
, unwrapSuccess
y unwrapError
ya no son opciones compatibles.
Además, las solicitudes ya no tienen semántica m.startComputation
/m.endComputation
. En cambio, los redibujados siempre se activan cuando se completa una cadena de promesas de solicitud (a menos que se establezca background: true
).
El parámetro data
ahora se ha dividido en params
, parámetros de consulta interpolados en la URL y agregados a la solicitud, y body
, el cuerpo para enviar en el XHR subyacente.
En la versión 0.2.x, usaría dataType: "jsonp"
para iniciar una solicitud JSONP. En la versión 2.x, ahora usa m.jsonp
, que tiene casi la misma API que m.request
sin las partes relacionadas con 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); // Nota: no es un getter-setter
}, 1000);
m.request({
method: 'POST',
url: 'https://api.github.com/',
body: someJson,
});
// O bien
var data = [];
m.request('https://api.github.com/').then(function (responseBody) {
data = responseBody;
});
setTimeout(function () {
console.log(data); // Nota: no es un getter-setter
}, 1000);
m.request('https://api.github.com/', {
method: 'POST',
body: someJson,
});
Además, si la opción extract
se pasa a m.request
, el valor de retorno de la función proporcionada se utilizará directamente para resolver la promesa de solicitud y se ignora la devolución de llamada deserialize
.
Encabezados de m.request
En la versión 0.2.x, Mithril.js no establecía ningún encabezado en las solicitudes de forma predeterminada. Ahora, establece hasta dos encabezados:
Content-Type: application/json; charset=utf-8
para solicitudes con cuerpos JSON que son!= null
Accept: application/json, text/*
para solicitudes que esperan respuestas JSON
El primero de los dos encabezados, Content-Type
, activará una búsqueda previa de CORS, ya que no es un encabezado de solicitud seguro para CORS debido al tipo de contenido especificado, y eso podría introducir nuevos errores dependiendo de cómo esté configurado CORS en su servidor. Si tiene problemas con esto, es posible que deba anular ese encabezado en cuestión pasando headers: {"Content-Type": "text/plain"}
. (El encabezado Accept
no activa nada, por lo que no necesita anularlo).
Los únicos tipos de contenido que la especificación Fetch permite evitar las comprobaciones de búsqueda previa de CORS son application/x-www-form-urlencoded
, multipart/form-data
y text/plain
. No permite nada más y, intencionalmente, no permite JSON.
m.deferred
eliminado
La versión 0.2.x usó su propio objeto de contrato asíncrono personalizado, expuesto como m.deferred
, que se usó como base para m.request
. La versión 2.x usa Promises en su lugar e implementa un polyfill en entornos no compatibles. En situaciones en las que habría utilizado m.deferred
, debería utilizar Promises en su lugar.
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);
}); //registra "hello world" después de 1 segundo
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);
}); //muestra "hello world" después de 1 segundo
m.sync
eliminado
Dado que la versión 2.x usa Promises que cumplen con los estándares, m.sync
es redundante. Use Promise.all
en su lugar.
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);
});
Se requiere el espacio de nombres xlink
En la versión 0.2.x, el espacio de nombres xlink
era el único espacio de nombres de atributo admitido, y se admitía a través de un comportamiento especial. Ahora, el análisis de espacios de nombres está completamente soportado y los atributos con espacio de nombres deben declarar explícitamente su espacio de nombres.
v0.2.x
m(
'svg',
// el espacio de nombres del atributo `href` se asigna automáticamente
m("image[href='image.gif']")
);
v2.x
m(
'svg',
// Espacio de nombres del atributo `href` especificado por el usuario
m("image[xlink:href='image.gif']")
);
Matrices anidadas en las vistas
Las matrices ahora representan fragmentos, que son estructuralmente significativos en el DOM virtual de la versión 2.x. Mientras que las matrices anidadas en la versión 0.2.x se aplanarían en una lista continua de nodos virtuales para fines de diferenciación, la versión 2.x conserva la estructura de la matriz: los elementos de cualquier matriz dada no se consideran hermanos de los elementos de matrices adyacentes.
Comprobaciones de igualdad de vnode
Si un vnode es estrictamente igual al vnode que ocupa su lugar en el último dibujo, la versión 2.x omitirá esa parte del árbol sin verificar si hubo cambios ni activar ningún método de ciclo de vida en el subárbol. La documentación del componente contiene más detalles sobre este problema.