Migração da v0.2.x
As versões v1.x e v2.x são amplamente compatíveis com a API da v0.2.x, mas existem algumas mudanças que quebram a compatibilidade. A migração para a v2.x é quase idêntica à da v1.x, portanto, as notas abaixo se aplicam principalmente a ambas.
Se você estiver migrando, considere usar a ferramenta mithril-codemods para ajudar a automatizar as migrações mais diretas.
m.prop
removido
Na v2.x, m.prop()
foi substituído por uma microbiblioteca de streams mais poderosa, mas não faz mais parte do núcleo. Você pode ler sobre como usar o módulo Streams opcional na documentação.
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
removido
Na v0.2.x, os componentes podiam ser criados usando m(Component)
ou m.component(Component)
. A v2.x suporta apenas m(Component)
.
v0.2.x
// Estes são equivalentes
m.component(Component);
m(Component);
v2.x
m(Component);
m.withAttr
removido
Na v0.2.x, os listeners de eventos podiam usar oninput: m.withAttr("value", func)
e similares. Na v2.x, basta ler os valores diretamente do atributo target
do evento. Ele funcionava bem com m.prop
, mas como este foi removido em favor de uma solução fora do núcleo e a v1.x não teve um uso idiomático amplo semelhante de streams, m.withAttr
perdeu a maior parte de sua utilidade.
v0.2.x
var value = m.prop('');
// In your view
m('input[type=text]', {
value: value(),
oninput: m.withAttr('value', value),
});
v2.x
var value = '';
// In your view
m('input[type=text]', {
value: value,
oninput: function (ev) {
value = ev.target.value;
},
});
m.version
removido
Tinha pouca utilidade em geral, e você sempre pode adicioná-lo de volta você mesmo. É preferível a detecção de recursos para saber quais funcionalidades estão disponíveis, e a API da v2.x foi projetada para habilitar melhor isso.
Função config
Na v0.2.x, o Mithril.js fornecia um único método de ciclo de vida, config
. A v2.x fornece um controle muito mais granular sobre o ciclo de vida de um vnode (nó virtual).
v0.2.x
m('div', {
config: function (element, isInitialized) {
// executa em cada atualização
// isInitialized é um booleano que indica se o nó foi adicionado ao DOM
},
});
v2.x
Mais documentação sobre esses novos métodos está disponível em lifecycle-methods.md.
m('div', {
// Chamado antes que o nó do DOM seja criado
oninit: function (vnode) {
/*...*/
},
// Chamado depois que o nó do DOM é criado
oncreate: function (vnode) {
/*...*/
},
// Chamado antes que o nó seja atualizado, retorna false para cancelar
onbeforeupdate: function (vnode, old) {
/*...*/
},
// Chamado depois que o nó é atualizado
onupdate: function (vnode) {
/*...*/
},
// Chamado antes que o nó seja removido, retorna uma Promise que resolve quando
// pronto para que o nó seja removido do DOM
onbeforeremove: function (vnode) {
/*...*/
},
// Chamado antes que o nó seja removido, mas depois que onbeforeremove chama done()
onremove: function (vnode) {
/*...*/
},
});
Se disponível, o elemento DOM do vnode pode ser acessado em vnode.dom
.
Mudanças no comportamento de redesenho
O mecanismo de renderização do Mithril.js ainda opera com base em redesenhos globais semiautomatizados, mas algumas APIs e comportamentos diferem:
Sem mais bloqueios de redesenho
Na v0.2.x, o Mithril.js permitia 'bloqueios de redraw' que impediam temporariamente a lógica de desenho: por padrão, m.request
bloqueava o loop de redraw na execução e desbloqueava quando todas as requisições pendentes fossem resolvidas - o mesmo comportamento podia ser invocado manualmente usando m.startComputation()
e m.endComputation()
. As últimas APIs e o comportamento associado foram removidos na v2.x sem substituição. O bloqueio de redraw pode levar a interfaces de usuário com bugs: as preocupações de uma parte do aplicativo não devem impedir que outras partes da view sejam atualizadas para refletir as mudanças.
Cancelando o redesenho de manipuladores de eventos
m.mount()
e m.route()
ainda redesenham automaticamente após a execução de um manipulador de eventos DOM. O cancelamento desses redesenhos de dentro de seus manipuladores de eventos agora é feito definindo a propriedade redraw
no objeto de evento passado como false
.
v0.2.x
m('div', {
onclick: function (e) {
m.redraw.strategy('none');
},
});
v2.x
m('div', {
onclick: function (e) {
e.redraw = false;
},
});
Redesenho síncrono alterado
Na v0.2.x, era possível forçar o Mithril.js a redesenhar imediatamente passando um valor truthy para m.redraw()
. Na v2.x, essa funcionalidade foi dividida em dois métodos diferentes para maior clareza.
v0.2.x
m.redraw(true); // redesenha imediatamente de forma síncrona
v2.x
m.redraw(); // agenda um redesenho no próximo ciclo de requestAnimationFrame
m.redraw.sync(); // invoca um redesenho imediatamente e aguarda sua conclusão
m.startComputation
/m.endComputation
removido
Eles são considerados antipadrões e têm vários casos extremos problemáticos, então foram removidos sem substituição na v2.x.
Função controller
do componente
Na v2.x, a propriedade controller
não existe mais nos componentes - use oninit
em vez disso.
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);
},
});
// OU
m.mount(document.body, {
// isto é vinculado a vnode.state por padrão
oninit: function (vnode) {
this.fooga = 1;
},
view: function (vnode) {
return m('p', this.fooga);
},
});
Argumentos do componente
Os argumentos para um componente na v2.x devem ser um objeto. Valores simples como String
/Number
/Boolean
serão tratados como text nodes. Os argumentos são acessados dentro do componente ao lê-los da propriedade attrs
do objeto vnode
.
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 }));
Filhos vnode do componente
Na v0.2.x, os filhos vnode do componente não eram normalizados, apenas passados como argumentos extras, e também não eram achatados. (Internamente, era apenas retornar um componente parcialmente aplicado que era diferenciado com base no componente sendo parcialmente aplicado.) Na v2.x, os filhos vnode do componente são passados via vnode.children
como um array resolvido de filhos, mas como na v0.2.x, os filhos individuais em si não são normalizados, nem o array de filhos é achatado.
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';
})
);
Filhos vnode DOM
Na v0.2.x, os filhos dos nós DOM eram representados literalmente, sem normalização, além de usar os filhos diretamente se apenas um único filho de array estivesse presente. Ele retornava uma estrutura mais parecida com esta, com as strings representadas literalmente.
m("div", "value", ["nested"])
// Transforma-se em:
{
tag: "div",
attrs: {},
children: [
"value",
["nested"],
]
}
Na v2.x, os filhos de vnodes DOM são normalizados para objetos de uma única estrutura consistente.
m("div", "value", ["nested"])
// Transforma-se aproximadamente em:
{
tag: "div",
attrs: null,
children: [
{tag: "#", children: "value"},
{tag: "[", children: [
{tag: "#", children: "nested"},
]},
]
}
Se apenas um único filho de texto estiver presente em um vnode DOM, ele definirá text
para esse valor.
m("div", "value")
// Transforma-se aproximadamente em:
{
tag: "div",
attrs: null,
text: "",
children: undefined,
}
Consulte the vnode docs para obter mais detalhes sobre a estrutura vnode da v2.x e como as coisas são normalizadas.
A maioria das propriedades vnode da v2.x aqui são omitidas por brevidade.
Chaves (keys)
Na v0.2.x, você podia misturar vnodes com e sem chave livremente.
Na v2.x, as listas de filhos de fragmentos e elementos devem ser todas com chave ou todas sem chave. Valores vazios também são considerados sem chave para fins desta verificação - ele não os ignora mais.
Se você precisar contornar isso, use a construção de um fragmento contendo um único vnode, como [m("div", {key: whatever})]
.
Parâmetros view()
Na v0.2.x, as funções de view recebem uma referência à instância do controller
e (opcionalmente) quaisquer opções passadas para o componente. Na v2.x, a função view
recebe apenas o vnode
como argumento, assim como a função 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 em vez de controlador
// Use vnode.attrs em vez de options
},
});
Passando componentes para m()
Na v0.2.x, você podia passar componentes como o segundo argumento de m()
sem nenhum encapsulamento necessário. Para manter a consistência na v2.x, os componentes devem sempre ser envolvidos em uma chamada de m()
.
v0.2.x
m('div', Component);
v2.x
m('div', m(Component));
Passando vnodes para m.mount()
e m.route()
Na v0.2.x, m.mount(element, component)
tolerava vnodes como segundos argumentos em vez de components (mesmo que não estivesse documentado). Da mesma forma, m.route(element, defaultRoute, routes)
aceitava vnodes como valores no objeto routes
.
Na v2.x, componentes são obrigatórios em ambos os 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
Na versão 0.2.x, o modo de roteamento podia ser definido atribuindo uma string de "pathname"
, "hash"
ou "search"
para m.route.mode
. Na v.1.x
, ele foi substituído por m.route.prefix = prefix
, onde prefix
pode ser qualquer prefixo. Se começar com #
, opera no modo "hash", ?
para o modo "search" e qualquer outro caractere (ou a string vazia) para o modo "pathname". Também suporta combinações do acima, como m.route.prefix = "/path/#!"
ou ?#
.
O padrão foi alterado para usar um prefixo #!
(hashbang) em vez de apenas #
. Portanto, se você usava o comportamento padrão e deseja manter seus URLs existentes, especifique m.route.prefix = "#"
antes de inicializar as rotas.
v0.2.x
m.route.mode = 'hash';
m.route.mode = 'pathname';
m.route.mode = 'search';
v2.x
// Equivalentes diretos
m.route.prefix = '#';
m.route.prefix = '';
m.route.prefix = '?';
m.route()
e tags de âncora
O tratamento de links roteáveis agora usa um componente especial integrado em vez de um atributo. Se você usava isso em <button>
s e similares, você pode especificar o nome da tag usando um atributo selector: "button"
.
v0.2.x
// Ao clicar, este link carregará a rota "/path" em vez de navegar
m('a', {
href: '/path',
config: m.route,
});
v2.x
// Ao clicar, este link carregará a rota "/path" em vez de navegar
m(m.route.Link, {
href: '/path',
});
Templates de caminho
Na v1.x, existiam três sintaxes de template de caminho separadas que, embora fossem semelhantes, tinham 2 sintaxes projetadas separadamente e 3 implementações diferentes. Era definido de forma ad hoc e os parâmetros geralmente não eram escapados. Agora, tudo é codificado se for :key
, e não codificado se for :key...
. Se valores estiverem sendo codificados inesperadamente, use :path...
. É simples assim.
Concretamente, isso afeta cada método da seguinte forma:
URLs m.request
Os componentes de caminho na v2.x são escapados automaticamente quando interpolados e obtêm seus valores de params
. Na v0.2.x, m.request({url: "/user/:name/photos/:id", data: {name: "a/b", id: "c/d"}})
enviaria sua requisição com o URL definido como /user/a%2Fb/photos/c/d
. Na v2.x, o correspondente m.request({url: "/user/:name/photos/:id", params: {name: "a/b", id: "c/d"}})
enviaria sua requisição para /user/a%2Fb/photos/c%2Fd
. Se você deliberadamente quiser interpolar uma chave não escapada, use :key...
em vez disso.
Interpolações em query strings inline, como em /api/search?q=:query
, não são realizadas na v2.x. Passe-os via params
com nomes de chave apropriados em vez disso, sem especificá-lo na query string.
Note que isso também se aplica a m.jsonp
. Ao migrar de m.request
+ dataType: "jsonp"
para m.jsonp
, você também precisa estar ciente disso.
Caminhos m.route(route, params, shouldReplaceHistoryEntry)
Agora eles permitem interpolações e funcionam de forma idêntica ao de m.request
.
Padrões de rota m.route
Keys de caminho da forma :key...
retornam seu URL decodificado na v1.x, mas retornam o URL bruto na v2.x.
Anteriormente, coisas como :key.md
eram erroneamente aceitas, com o valor do parâmetro resultante definido como keymd: "..."
. Este não é mais o caso - o .md
faz parte do padrão agora, não do nome.
Lendo/escrevendo a rota atual
Na v0.2.x, toda interação com a rota atual acontecia via m.route()
. Na v2.x, isso foi dividido em duas funções.
v0.2.x
// Obtendo a rota atual
m.route();
// Definindo uma nova rota
m.route('/other/route');
v2.x
// Obtendo a rota atual
m.route.get();
// Definindo uma nova rota
m.route.set('/other/route');
Acessando parâmetros de rota
Na v0.2.x, a leitura dos parâmetros de rota era totalmente tratada através de m.route.param()
. Esta API ainda está disponível na v2.x e, além disso, quaisquer parâmetros de rota são passados como propriedades no objeto attrs
no 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"
},
},
});
Construindo/Analisando query strings
Na v0.2.x, eram utilizados os métodos m.route.buildQueryString()
e m.route.parseQueryString()
, que pertenciam a m.route
. Na v2.x, estes foram divididos e movidos para a raiz 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');
Além disso, na v2.x, {key: undefined}
é serializado como key=undefined
por m.buildQueryString
e métodos que o utilizam como m.request
. Na v0.2.x, a key era omitida e isso se estendia a m.request
. Se você estava confiando nisso anteriormente, altere seu código para omitir as keys do objeto completamente. Pode ser útil utilizar uma função auxiliar simples para remover todas as keys de um objeto cujos valores sejam undefined
, caso você não consiga fazer isso facilmente e precise manter o comportamento da v0.2.x.
// Chame sempre que precisar omitir parâmetros `undefined` de um 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;
}
Prevenindo a desmontagem
Não é mais possível impedir a desmontagem via e.preventDefault()
de onunload
. Em vez disso, você deve chamar explicitamente m.route.set
quando as condições esperadas forem satisfeitas.
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('/');
},
});
},
};
Executar código na remoção do componente
Os componentes não chamam mais this.onunload
quando estão sendo excluídos. Eles agora usam o hook de ciclo de vida padronizado onremove
.
v0.2.x
var Component = {
controller: function () {
this.onunload = function (e) {
// ...
};
},
view: function () {
// ...
},
};
v2.x
var Component = {
onremove: function() {
// ...
}
view: function() {
// ...
}
}
m.request
Promises retornadas por m.request não são mais getters e setters m.prop
. Além disso, initialValue
, unwrapSuccess
e unwrapError
não são mais opções suportadas.
Além disso, as requisições não têm mais semântica m.startComputation
/m.endComputation
. Em vez disso, as redesenhos são sempre acionadas quando uma cadeia de promises de requisição é concluída (a menos que background: true
seja definido).
O parâmetro data
agora foi dividido em params
(parâmetros de query interpolados no URL e anexados à requisição) e body
(o corpo enviado na requisição XHR subjacente).
Na v0.2.x, você usaria dataType: "jsonp"
para iniciar uma requisição JSONP. Na v2.x, você agora usa m.jsonp
, que possui basicamente a mesma API que m.request
sem as partes relacionadas ao 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); // observe: não é um getter-setter
}, 1000);
m.request({
method: 'POST',
url: 'https://api.github.com/',
body: someJson,
});
// OU
var data = [];
m.request('https://api.github.com/').then(function (responseBody) {
data = responseBody;
});
setTimeout(function () {
console.log(data); // observe: não é um getter-setter
}, 1000);
m.request('https://api.github.com/', {
method: 'POST',
body: someJson,
});
Adicionalmente, se a opção extract
for passada para m.request
, o valor de retorno da função fornecida será usado diretamente para resolver a promise da requisição, e o callback deserialize
é ignorado.
Headers m.request
Na v0.2.x, Mithril.js não definia nenhum header nas requisições por padrão. Agora, ele define até 2 headers:
Content-Type: application/json; charset=utf-8
para requisições com corpos JSON que são!= null
Accept: application/json, text/*
para requisições esperando respostas JSON
O primeiro dos dois headers, Content-Type
, irá acionar um prefetch CORS, pois não é um header de requisição CORS-safelisted devido ao tipo de conteúdo especificado, o que pode introduzir novos erros dependendo de como o CORS está configurado em seu servidor. Se você encontrar problemas com isso, pode ser necessário sobrescrever este header em questão, passando headers: {"Content-Type": "text/plain"}
. (O header Accept
não dispara nada, então você não precisa substituí-lo.)
Os únicos tipos de conteúdo que a especificação Fetch permite evitar verificações de prefetch CORS são application/x-www-form-urlencoded
, multipart/form-data
e text/plain
. Não permite outros tipos e intencionalmente não permite JSON.
m.deferred
removido
A v0.2.x usava seu próprio objeto de contrato assíncrono personalizado, exposto como m.deferred
, que era usado como base para m.request
. A v2.x usa Promises em vez disso e implementa um polyfill em ambientes não suportados. Em situações em que você teria usado m.deferred
, você deve usar Promises em vez disso.
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);
}); // exibe "hello world" após 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);
}); // exibe "hello world" após 1 segundo
m.sync
removido
Como a v2.x usa Promises compatíveis com os padrões estabelecidos, m.sync
tornou-se redundante. Use Promise.all
em vez disso.
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
requerido
Na v0.2.x, o namespace xlink
era o único namespace de atributo suportado e era suportado por meio de um comportamento de tratamento de caso especial. Agora, o parsing de namespace é totalmente suportado e os atributos com namespace devem declarar explicitamente seu namespace.
v0.2.x
m(
'svg',
// the `href` attribute is namespaced automatically
m("image[href='image.gif']")
);
v2.x
m(
'svg',
// User-specified namespace on the `href` attribute
m("image[xlink:href='image.gif']")
);
Arrays aninhados em views
Arrays agora representam fragmentos, que possuem significado estrutural no DOM virtual da v2.x. Enquanto arrays aninhados na v0.2.x seriam achatados em uma lista contínua de nós virtuais para efeitos de diffing, a v2.x preserva a estrutura do array - os filhos de qualquer array dado não são considerados irmãos daqueles de arrays adjacentes.
Verificações de igualdade vnode
Se um vnode é estritamente igual ao vnode que ocupa seu lugar no último desenho, a v2.x irá ignorar essa parte da árvore sem verificar mutações ou acionar quaisquer métodos de ciclo de vida na subárvore. A documentação do componente contém mais detalhes sobre este problema.