Migrando desde v1.x
La v2.x es casi completamente compatible con la API de v1.x, pero existen algunos cambios significativos.
Asignación a vnode.state
En v1.x, se podía manipular vnode.state
y asignar cualquier valor. En v2.x, se lanzará un error si se intenta modificarlo directamente. La migración puede variar, pero en la mayoría de los casos, es tan simple como cambiar las referencias de vnode.state
a vnode.state.foo
, eligiendo un nombre apropiado para foo
(como count
, si representa el valor actual de un contador).
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++;
},
},
'+'
),
]);
},
};
Cuando se lanzó v1.0 por primera vez, los componentes de clase y de closure no existían, por lo que simplemente se extraía lo que se necesitaba de vnode.tag
. Este detalle de implementación es lo que permitía hacerlo, y algunos comenzaron a depender de ello. También se insinuaba como posible en algunos lugares dentro de la documentación. Ahora, las cosas son diferentes, y esto facilita la administración desde un punto de vista de implementación, ya que solo hay una referencia al estado, no dos.
Cambios en los enlaces de ruta
En v1.x, se utilizaba oncreate: m.route.link
y, si el enlace podía cambiar, onupdate: m.route.link
también, como ciclos de vida en el vnode que se podía enrutar. En v2.x, ahora se utiliza un componente m.route.Link
. El selector se puede especificar a través de un atributo selector:
, en caso de que se estuviera usando algo diferente a m("a", ...)
, las opciones se pueden especificar a través de options:
, se puede deshabilitar a través de disabled:
, y otros atributos se pueden especificar en línea, incluyendo href:
(obligatorio). El selector:
en sí mismo puede contener cualquier selector válido como el primer argumento para m
, y los atributos [href=...]
y [disabled]
se pueden especificar tanto en el selector como en las opciones normales.
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]',
});
Cambios en los errores de m.request
En v1.x, m.request
analizaba los errores de las llamadas JSON y asignaba las propiedades del objeto resultante analizado a la respuesta. Entonces, si se recibía una respuesta con el estado 403 y un cuerpo de {"code": "backoff", "timeout": 1000}
, el error tendría dos propiedades adicionales: err.code = "backoff"
y err.timeout = 1000
.
En v2.x, la respuesta se asigna a una propiedad response
en el resultado, y una propiedad code
contiene el código de estado resultante. Entonces, si se recibía una respuesta con el estado 403 y un cuerpo de {"code": "backoff", "timeout": 1000}
, el error tendría asignadas dos propiedades: err.response = {code: "backoff", timeout: 1000}
y err.code = 403
.
m.withAttr
eliminado
En v1.x, los listeners de eventos podían usar oninput: m.withAttr("value", func)
y similares. En v2.x, simplemente se leen directamente del elemento objetivo del evento. Se integraba bien con los streams, pero dado que el uso de m.withAttr("value", stream)
no era ni de lejos tan común como m.withAttr("value", prop)
, m.withAttr
perdió la mayor parte de su utilidad y, por lo tanto, se eliminó.
v1.x
var value = '';
// En tu vista
m('input[type=text]', {
value: value(),
oninput: m.withAttr('value', function (v) {
value = v;
}),
});
// O
var value = m.stream('');
// 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;
},
});
// O
var value = m.stream('');
// En tu vista
m('input[type=text]', {
value: value(),
oninput: function (ev) {
value(ev.target.value);
},
});
m.route.prefix
En v1.x, m.route.prefix
era una función que se llamaba a través de m.route.prefix(prefix)
. Ahora es una propiedad que se establece a través de m.route.prefix = prefix
v1.x
m.route.prefix('/root');
v2.x
m.route.prefix = '/root';
Parámetros y cuerpo de m.request
/m.jsonp
data
y useBody
se refactorizaron en params
(parámetros de consulta interpolados en la URL y añadidos a la petición) y body
(el cuerpo para enviar en el XHR subyacente). Esto proporciona un mejor control sobre la petición real enviada y permite tanto interpolar en los parámetros de consulta con las peticiones POST
como crear peticiones GET
con cuerpos.
m.jsonp
, al no tener un "cuerpo" significativo, simplemente usa params
, por lo que renombrar data
a params
es suficiente para ese método.
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,
});
Plantillas de ruta
En v1.x, había tres sintaxis separadas de plantilla de ruta que, aunque eran similares, tenían 2 sintaxis diseñadas por separado y 3 implementaciones diferentes. Se definía de una manera bastante ad-hoc, y los parámetros generalmente no se escapaban. Ahora, todo se codifica si es :key
, sin formato si es :key...
. Si las cosas se codifican inesperadamente, usa :path...
. Es así de simple.
Concretamente, así es como afecta a cada método:
URLs de m.request
y m.jsonp
, rutas de m.route.set
Los componentes de ruta en v2.x se escapan automáticamente cuando se interpolan. Supongamos que se invoca m.route.set("/user/:name/photos/:id", {name: user.name, id: user.id})
. Anteriormente, si user
era {name: "a/b", id: "c/d"}
, esto establecería la ruta a /user/a%2Fb/photos/c/d
, pero ahora la establecerá a /user/a%2Fb/photos/c%2Fd
. Si deliberadamente quieres interpolar una clave sin escapar, usa :key...
en su lugar.
Las claves en v2.x no pueden contener ninguna instancia de .
o -
. En v1.x, podían contener cualquier cosa que no fuera /
.
Las interpolaciones en cadenas de consulta en línea, como en /api/search?q=:query
, no se realizan en v2.x. Pásalas a través de params
con los nombres de clave apropiados en su lugar, sin especificarlos en la cadena de consulta.
Patrones de ruta de m.route
Las claves de ruta de la forma :key...
devuelven su URL decodificada en v1.x, pero devuelven la URL sin formato en v2.x.
Anteriormente, cosas como :key.md
se aceptaban erróneamente, con el valor del parámetro resultante establecido en keymd: "..."
. Este ya no es el caso: el .md
ahora es parte del patrón, no del nombre.
Orden de llamada del ciclo de vida
En v1.x, los ciclos de vida de los atributos en los vnodes de los componentes se llamaban antes que los ciclos de vida propios del componente en todos los casos. En v2.x, este es el caso solo para onbeforeupdate
. Por lo tanto, es posible que se deba ajustar el código en consecuencia.
v1.x
var Comp = {
oncreate: function () {
console.log('Component oncreate'); // Componente oncreate
},
view: function () {
return m('div');
},
};
m.mount(document.body, {
view: function () {
return m(Comp, {
oncreate: function () {
console.log('Attrs oncreate'); // Atributos oncreate
},
});
},
});
// Logs:
// Attrs oncreate
// Component oncreate
v2.x
var Comp = {
oncreate: function () {
console.log('Component oncreate'); // Componente oncreate
},
view: function () {
return m('div');
},
};
m.mount(document.body, {
view: function () {
return m(Comp, {
oncreate: function () {
console.log('Attrs oncreate'); // Atributos oncreate
},
});
},
});
// Logs:
// Component oncreate
// Attrs oncreate
Sincronía de m.redraw
m.redraw()
en v2.x siempre es asíncrono. Se puede solicitar específicamente un redraw síncrono a través de m.redraw.sync()
siempre que no se esté produciendo actualmente ningún redraw.
Precedencia de los atributos del selector
En v1.x, los atributos del selector tenían prioridad sobre los atributos especificados en el objeto de atributos. Por ejemplo, m("[a=b]", {a: "c"}).attrs
devolvía {a: "b"}
.
En v2.x, los atributos especificados en el objeto de atributos tienen prioridad sobre los atributos del selector. Por ejemplo, m("[a=b]", {a: "c"}).attrs
devuelve {a: "c"}
.
Ten en cuenta que esto técnicamente revierte al comportamiento de v0.2.x.
Normalización de los hijos
En v1.x, los hijos de los vnodes de los componentes se normalizaban como los demás vnodes. En v2.x, este ya no es el caso y se deberá planificar en consecuencia. Esto no afecta la normalización realizada en el renderizado.
Encabezados de m.request
En v1.x, Mithril.js establecía estos dos encabezados en todas las peticiones que no eran GET
, pero solo cuando useBody
se establecía en true
(el valor predeterminado) y se cumplían las otras condiciones enumeradas:
Content-Type: application/json; charset=utf-8
para peticiones con cuerpos JSONAccept: application/json, text/*
para peticiones que esperan respuestas JSON
En v2.x, Mithril.js establece el primero para todas las peticiones con cuerpos JSON que son != null
y lo omite de forma predeterminada en caso contrario, y esto se hace independientemente del método que se elija, incluso en las peticiones GET
.
El primero de los dos encabezados, Content-Type
, activará una pre-petición CORS ya que no es un encabezado de petición seguro para CORS debido al tipo de contenido especificado, y eso podría introducir nuevos errores dependiendo de cómo esté configurado CORS en el servidor. Si se tienen problemas con esto, es posible que se deba anular ese encabezado en cuestión pasando headers: {"Content-Type": "text/plain"}
. (El encabezado Accept
no activa nada, por lo que no es necesario anularlo).
Los únicos tipos de contenido que la especificación Fetch permite evitar las comprobaciones previas a la petición CORS son application/x-www-form-urlencoded
, multipart/form-data
y text/plain
. No permite nada más, y prohíbe intencionalmente JSON.
Parámetros de consulta en cadenas hash en rutas
En v1.x, se podían especificar parámetros de consulta para rutas tanto en la cadena de consulta como en la cadena hash, por lo que m.route.set("/route?foo=1&bar=2")
, m.route.set("/route?foo=1#bar=2")
y m.route.set("/route#foo=1&bar=2")
eran todos equivalentes y los atributos extraídos de ellos habrían sido {foo: "1", bar: "2"}
.
En v2.x, el contenido de las cadenas hash se ignora pero se conserva. Entonces, los atributos extraídos de cada uno serían estos:
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")
→{}
La razón para hacer esto es que las URLs como https://example.com/#!/route#key
son técnicamente inválidas según la especificación de URL e incluso eran inválidas según el RFC que la precedió, y es solo una peculiaridad de la especificación HTML que se permitan. (La especificación HTML debería haber requerido que los IDs y los fragmentos de ubicación fueran fragmentos de URL válidos desde el principio si quería seguir la especificación).
O en resumen, ¡deja de usar URLs inválidas!
Claves
En v1.x, se podían mezclar vnodes con clave y sin clave libremente. Si el primer nodo tiene clave, se realiza una diferencia con clave, asumiendo que cada elemento tiene una clave y simplemente ignorando los agujeros a medida que avanza. De lo contrario, se realiza una diferencia iterativa, y si un nodo tiene una clave, se verificaría que no haya cambiado al mismo tiempo que se verifican las etiquetas y similares.
En v2.x, las listas de hijos tanto de fragmentos como de elementos deben ser todas con clave o todas sin clave. Los agujeros también se consideran sin clave para los propósitos de esta verificación; ya no los ignora.
Si se necesita solucionar esto, se puede usar el modismo de un fragmento que contiene un solo vnode, como [m("div", {key: whatever})]
.
m.version
eliminado
Tenía poco uso en general, y siempre se puede volver a añadir por cuenta propia. Se debe preferir la detección de características para saber qué características están disponibles, y la API v2.x está diseñada para habilitar mejor esto.