Migrando da v1.x
A v2.x é quase totalmente compatível com a API da v1.x, mas existem algumas mudanças que quebram a compatibilidade.
Atribuição a vnode.state
Na v1.x, era possível manipular vnode.state
e atribuir qualquer valor desejado. Na v2.x, um erro será lançado se o valor de vnode.state
for alterado diretamente. A migração pode variar, mas na maioria dos casos, é tão simples quanto mudar as referências de vnode.state
para vnode.state.foo
, escolhendo um nome apropriado para foo
(como count
, se for o valor atual de um 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++;
},
},
'+'
),
]);
},
};
Quando a v1.0 foi lançada, componentes de classe e closure não existiam, então o framework acessava o que precisava de vnode.tag
. Este detalhe de implementação permitiu essa manipulação, e alguns desenvolvedores começaram a depender dela. Isso também foi sugerido como possível em alguns trechos da documentação. Agora, as coisas são diferentes, e isso torna o gerenciamento mais fácil do ponto de vista da implementação, pois há apenas uma referência ao estado, não duas.
Mudanças nas âncoras de rota
Na v1.x, você usava oncreate: m.route.link
e, se o link pudesse mudar, onupdate: m.route.link
também, cada um como hooks de ciclo de vida no vnode que poderia ser roteado. Na v2.x, você deve usar um componente m.route.Link
. O seletor pode ser especificado via atributo selector:
, caso você utilize um elemento diferente de <a>
. As opções podem ser especificadas via options:
, você pode desabilitá-lo através de disabled:
, e outros atributos podem ser especificados inline, incluindo href:
(obrigatório). O próprio selector:
pode conter qualquer seletor válido como o primeiro argumento para m
, e os atributos [href=...]
e [disabled]
podem ser especificados no seletor, bem como nas opções normais.
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]',
});
Mudanças nos erros de m.request
Na v1.x, m.request
analisava erros de chamadas JSON e atribuía as propriedades do objeto analisado resultante à resposta. Então, se você receber uma resposta com status 403 e um corpo de {"code": "backoff", "timeout": 1000}
, o objeto de erro terá duas propriedades: err.response = {code: "backoff", timeout: 1000}
e err.code = 403
.
Na v2.x, a resposta é atribuída a uma propriedade response
no resultado, e uma propriedade code
contém o código de status resultante. Então, se você recebesse uma resposta com status 403 e um corpo de {"code": "backoff", "timeout": 1000}
, o erro teria duas propriedades: err.response = {code: "backoff", timeout: 1000}
e err.code = 403
.
m.withAttr
removido
Na v1.x, os listeners de evento podiam usar oninput: m.withAttr("value", func)
e similares. Na v2.x, basta lê-los diretamente da propriedade target
do evento. Ele funcionava bem com streams, mas como o uso de m.withAttr("value", stream)
não era nem de perto tão comum quanto m.withAttr("value", prop)
, m.withAttr
perdeu a maior parte de sua utilidade e, portanto, foi removido.
v1.x
var value = '';
// In your view
m('input[type=text]', {
value: value(),
oninput: m.withAttr('value', function (v) {
value = v;
}),
});
// OR
var value = m.stream('');
// 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;
},
});
// OR
var value = m.stream('');
// In your view
m('input[type=text]', {
value: value(),
oninput: function (ev) {
value(ev.target.value);
},
});
m.route.prefix
Na v1.x, m.route.prefix
era uma função chamada via m.route.prefix(prefix)
. Agora é uma propriedade que você define via m.route.prefix = prefix
.
v1.x
m.route.prefix('/root');
v2.x
m.route.prefix = '/root';
Parâmetros e corpo de m.request
/m.jsonp
As opções data
e useBody
foram substituídas por params
, que define os parâmetros de consulta interpolados na URL e anexados à requisição, e body
, que define o corpo da requisição a ser enviado no XHR subjacente. Isso oferece muito mais controle sobre a requisição real enviada e permite que você interpole parâmetros de query com requisições POST
e crie requisições GET
com corpos.
m.jsonp
, não tendo nenhum "corpo" significativo, apenas usa params
, então renomear data
para params
é suficiente para esse 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,
});
Templates de caminho
Na v1.x, havia três sintaxes de template de caminho separadas que, embora fossem semelhantes, tinham 2 sintaxes projetadas separadamente e 3 implementações diferentes. Foi definido de uma forma bastante ad-hoc, e os parâmetros geralmente não eram escapados. Agora, tudo é codificado se for :chave
, bruto se for :chave...
. Se a codificação ocorrer de forma inesperada, utilize :path...
. É simples assim.
Concretamente, veja como isso afeta cada método:
URLs de m.request
e m.jsonp
, caminhos de m.route.set
Os componentes de caminho na v2.x são escapados automaticamente quando interpolados. Suponha que você invoque m.route.set("/user/:name/photos/:id", {name: user.name, id: user.id})
. Anteriormente, se user
fosse {name: "a/b", id: "c/d"}
, isso definiria a rota para /user/a%2Fb/photos/c/d
, mas agora definirá para /user/a%2Fb/photos/c%2Fd
. Se você deseja deliberadamente interpolar uma chave sem escape, utilize :chave...
em vez disso.
As chaves na v2.x não podem conter nenhuma instância de .
ou -
. Na v1.x, eles podiam conter qualquer coisa que não fosse /
.
As 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.
Padrões de rota de m.route
As chaves de caminho da forma :chave...
retornam seu URL decodificado na v1.x, mas retornam o URL bruto na v2.x.
Anteriormente, coisas como :chave.md
eram erroneamente aceitas, com o valor do parâmetro resultante definido como chavemd: "..."
. Este não é mais o caso - o .md
faz parte do padrão agora, não do nome.
Ordem de chamada do ciclo de vida
Na v1.x, os lifecycle hooks de atributo em vnodes de componente eram chamados antes dos próprios lifecycle hooks do componente em todos os casos. Na v2.x, este é o caso apenas para onbeforeupdate
. Portanto, você pode precisar ajustar seu código de acordo.
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
Sincronia de m.redraw
m.redraw()
na v2.x é sempre assíncrono. Você pode solicitar especificamente um redesenho síncrono via m.redraw.sync()
desde que nenhum redesenho esteja ocorrendo atualmente.
Precedência de atributo de seletor
Na v1.x, os atributos de seletor tinham precedência sobre os atributos especificados no objeto de atributos. Por exemplo, m("[a=b]", {a: "c"}).attrs
retornava {a: "b"}
.
Na v2.x, os atributos especificados no objeto de atributos têm precedência sobre os atributos de seletor. Por exemplo, m("[a=b]", {a: "c"}).attrs
retorna {a: "c"}
.
Observe que isso é tecnicamente uma reversão ao comportamento da v0.2.x.
Normalização de filhos
Na v1.x, os filhos de vnode de componente eram normalizados como outros vnodes. Na v2.x, este não é mais o caso e você precisará planejar de acordo. Isso não afeta a normalização feita na renderização.
Headers de m.request
Na v1.x, Mithril.js definia esses dois cabeçalhos em todas as requisições não-GET
, mas apenas quando useBody
era definido como true
(o padrão) e as outras condições listadas se mantinham:
Content-Type: application/json; charset=utf-8
para requisições com corpos JSONAccept: application/json, text/*
para requisições esperando respostas JSON
Na v2.x, Mithril.js define o primeiro para todas as requisições com corpos JSON que são != null
e o omite por padrão caso contrário, e isso é feito independentemente de qual método é escolhido, incluindo em requisições GET
.
O primeiro dos dois cabeçalhos, Content-Type
, pode disparar uma requisição preflight do CORS, pois não é um cabeçalho de requisição CORS safelisted devido ao tipo de conteúdo especificado. Isso pode introduzir novos erros, dependendo da configuração do CORS no servidor. Se você tiver problemas com isso, pode ser necessário substituir esse cabeçalho em questão passando headers: {"Content-Type": "text/plain"}
. (O cabeçalho 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 preflight CORS são application/x-www-form-urlencoded
, multipart/form-data
e text/plain
. Não permite mais nada e intencionalmente não permite JSON.
Parâmetros de query em hash strings em rotas
Na v1.x, você podia especificar parâmetros de query para rotas tanto na query string quanto na hash string, então 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")
eram todos equivalentes e os atributos extraídos deles teriam sido {foo: "1", bar: "2"}
.
Na v2.x, o conteúdo das hash strings é ignorado, mas preservado. Então, os atributos extraídos de cada um seriam estes:
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")
→{}
A razão para essa mudança é que URLs como https://example.com/#!/route#key
são tecnicamente inválidas de acordo com a especificação de URL e já eram inválidas de acordo com a RFC anterior. A permissão para esse tipo de URL é apenas uma peculiaridade da especificação HTML.
Ou, em resumo, pare de usar URLs inválidos!
Chaves
Na v1.x, você podia misturar vnodes com chave e sem chave livremente. Se o primeiro nó tiver chave, uma diff com chave é realizada, assumindo que cada elemento tem uma chave e apenas ignorando buracos à medida que avança. Caso contrário, uma diff iterativa é realizada e, se um nó tiver uma chave, seria verificado se ele não mudou ao mesmo tempo em que tags e similares são verificados.
Na v2.x, as listas de filhos de fragmentos e elementos devem ser consistentemente chaveadas ou não chaveadas. Espaços vazios são considerados sem chave para fins desta verificação também - não os ignora mais.
Se você precisar contornar isso, use a prática de um fragmento contendo um único vnode, como [m("div", {key: whatever})]
.
m.version
removido
Tinha pouca utilidade em geral, e você sempre pode adicioná-lo de volta sozinho. Você deve preferir a detecção de recursos para saber quais recursos estão disponíveis, e a API v2.x foi projetada para melhor habilitar isso.