route(root, defaultRoute, routes)
Descrição
Permite a navegação entre "páginas" dentro de um aplicativo, sem recarregar a página.
var Home = {
view: function () {
return 'Welcome';
},
};
m.route(document.body, '/home', {
'/home': Home, // define `https://localhost/#!/home`
});
Você pode ter apenas uma chamada m.route
por aplicativo.
Assinatura
m.route(root, defaultRoute, routes)
Parâmetro | Tipo | Obrigatório | Descrição |
---|---|---|---|
root | Element | Sim | Um elemento DOM que será o nó pai da árvore de elementos renderizados pelo Mithril. |
defaultRoute | String | Sim | A rota para a qual redirecionar se a URL atual não corresponder a nenhuma rota definida. Note que esta não é a rota inicial; a rota inicial é determinada pela URL na barra de endereço do navegador. |
routes | Object<String,Component|RouteResolver> | Sim | Um objeto onde as chaves (keys) são strings de rota e os valores são componentes ou um RouteResolver. |
retorna | Retorna undefined . |
Membros estáticos
m.route.set
Redireciona para uma rota correspondente ou para a rota padrão se nenhuma rota correspondente for encontrada. Dispara uma atualização assíncrona de todos os pontos de montagem.
m.route.set(path, params, options)
Parâmetro | Tipo | Obrigatório | Descrição |
---|---|---|---|
path | String | Sim | O nome do caminho para o qual rotear, sem o prefixo. O caminho pode incluir parâmetros que são interpolados com valores de params . |
params | Object | Não | Parâmetros de roteamento. Se path contiver espaços reservados para parâmetros de roteamento, as propriedades deste objeto serão interpoladas na string do caminho. |
options.replace | Boolean | Não | Indica se uma nova entrada deve ser criada no histórico do navegador ou se a entrada atual deve ser substituída. O padrão é false . |
options.state | Object | Não | O objeto state a ser passado para a chamada subjacente history.pushState / history.replaceState . Este objeto de estado se torna disponível na propriedade history.state e é mesclado no objeto de parâmetros de roteamento. Observe que esta opção só funciona quando se usa a API pushState e é ignorada se o roteador utilizar o modo hashchange (ou seja, se a API pushState não estiver disponível). |
options.title | String | Não | A string title a ser passada para a chamada subjacente history.pushState / history.replaceState . |
retorna | Retorna undefined . |
Lembre-se de que, ao usar .set
com params
, você também precisa definir a rota correspondente:
var Article = {
view: function (vnode) {
return 'This is article ' + vnode.attrs.articleid;
},
};
m.route(document.body, {
'/article/:articleid': Article,
});
m.route.set('/article/:articleid', { articleid: 1 });
m.route.get
Retorna o último caminho de roteamento totalmente resolvido, sem o prefixo. Pode ser diferente do caminho exibido na barra de endereço enquanto uma rota assíncrona está aguardando resolução.
path = m.route.get()
Parâmetro | Tipo | Obrigatório | Descrição |
---|---|---|---|
retorna | String | Retorna o último caminho totalmente resolvido. |
m.route.prefix
Define o prefixo do roteador. O prefixo do roteador é um fragmento da URL que define a estratégia subjacente utilizada pelo Mithril.
m.route.prefix = prefix
Parâmetro | Tipo | Obrigatório | Descrição |
---|---|---|---|
prefix | String | Sim | O prefixo que controla a estratégia de roteamento subjacente usada pelo Mithril. |
Esta é uma propriedade simples, então você pode lê-la e escrever nela.
m.route.Link
Este componente cria um link roteado dinâmico. Sua principal função é gerar links <a>
com atributos href
que são adaptados de acordo com o prefixo de rota.
m(m.route.Link, { href: '/foo' }, 'foo');
// A menos que m.route.prefix tenha mudado da estratégia padrão, renderiza para:
// <a href="#!/foo">foo</a>
Os links aceitam uma seleção de atributos especiais:
selector
é o que seria passado como o primeiro argumento param
: qualquer seletor é válido, incluindo elementos que não são<a>
.params
&options
são os argumentos com os mesmos nomes definidos emm.route.set
.disabled
, se verdadeiro, desativa o comportamento de roteamento e qualquer manipuladoronclick
vinculado, e anexa um atributodata-disabled="true"
para dicas de acessibilidade. Se o elemento for um<a>
, o atributohref
é removido.
O comportamento de roteamento não pode ser impedido pela API de eventos: use disabled
em vez disso.
m(
m.route.Link,
{
href: '/foo',
selector: 'button.large',
disabled: true,
params: { key: 'value' },
options: { replace: true },
},
'link name'
);
// Renderiza para:
// <button disabled aria-disabled="true" class="large">link name</button>
vnode = m(m.route.Link, attributes, children)
Parâmetro | Tipo | Obrigatório | Descrição |
---|---|---|---|
attributes.href | Object | Sim | A rota de destino para a qual navegar. |
attributes.disabled | Boolean | Não | Desativa o elemento de forma acessível. |
attributes.selector | String|Object|Function | Não | Um seletor para m ; o padrão é "a" . |
attributes.options | Object | Não | Define as options passadas para m.route.set . |
attributes.params | Object | Não | Define os params passados para m.route.set . |
attributes | Object | Não | Quaisquer outros atributos a serem encaminhados para m . |
children | Array<Vnode>|String|Number|Boolean | Não | vnodes filhos para este link. |
retorna | Vnode | Um vnode. |
m.route.param
Obtém um parâmetro da última rota que foi completamente resolvida. Um parâmetro de rota é um par chave-valor. Os parâmetros de rota podem vir de diferentes fontes:
- Interpolações de rota (por exemplo, se uma rota for
/users/:id
e ela for resolvida para/users/1
, o parâmetro de rota tem uma chaveid
e o valor"1"
). - Querystrings da URL (por exemplo, se o caminho for
/users?page=1
, o parâmetro de rota tem uma chavepage
e o valor"1"
). history.state
(por exemplo, sehistory.state
for{foo: "bar"}
, o parâmetro de rota tem a chavefoo
e o valor"bar"
).
value = m.route.param(key)
Parâmetro | Tipo | Obrigatório | Descrição |
---|---|---|---|
key | String | Não | O nome de um parâmetro de rota (por exemplo, id na rota /users/:id ou page no caminho /users/1?page=3 ou uma chave em history.state ). |
retorna | String|Object | Retorna o valor para a chave especificada. Se nenhuma chave for especificada, retorna um objeto contendo todas as chaves utilizadas na interpolação. |
Observe que, na função onmatch
de um RouteResolver, a nova rota ainda não foi totalmente resolvida e m.route.param()
retornará os parâmetros da rota anterior, se houver. onmatch
recebe os parâmetros da nova rota como um argumento.
m.route.SKIP
Um valor especial que pode ser retornado pela função onmatch
de um resolvedor de rotas para que a próxima rota seja considerada.
RouteResolver
Um RouteResolver é um objeto que não é um componente e contém um método onmatch
e/ou um método render
. Ambos os métodos são opcionais, mas pelo menos um deve estar presente.
Se um objeto puder ser detectado como um componente (pela presença de um método view
ou por ser uma function
/class
), ele será tratado como tal, mesmo que tenha métodos onmatch
ou render
. Por não ser um componente, um RouteResolver não possui métodos de ciclo de vida.
Como regra geral, os RouteResolvers devem estar no mesmo arquivo que a chamada m.route
, enquanto as definições de componente devem estar em seus próprios módulos.
routeResolver = {onmatch, render}
Ao usar componentes, você pode pensar neles como um atalho para este resolvedor de rota, assumindo que seu componente seja Home
:
var routeResolver = {
onmatch: function () {
return Home;
},
render: function (vnode) {
return [vnode];
},
};
routeResolver.onmatch
A função onmatch
é executada quando o roteador precisa determinar qual componente deve ser renderizado. É executada uma vez por cada mudança de caminho do roteador, mas não em redesenhos subsequentes enquanto estiver no mesmo caminho. Pode ser utilizada para executar lógica antes que um componente seja inicializado (por exemplo, autenticação, carregamento prévio de dados, rastreamento de analytics para redirecionamento, etc.).
Este método também permite que você defina assincronamente qual componente será renderizado, tornando-o adequado para divisão de código e carregamento de módulo assíncrono. Para renderizar um componente de forma assíncrona, retorne uma promise que seja resolvida para um componente.
Para obter mais informações sobre onmatch
, consulte a seção resolução avançada de componente.
routeResolver.onmatch(args, requestedPath, route)
Parâmetro | Tipo | Descrição |
---|---|---|
args | Object | Os parâmetros de roteamento. |
requestedPath | String | O caminho do roteador solicitado pela última ação de roteamento, incluindo valores de parâmetro de roteamento interpolados, mas sem o prefixo. Quando onmatch é chamado, a resolução para este caminho não está completa e m.route.get() ainda retorna o caminho anterior. |
route | String | O caminho do roteador solicitado pela última ação de roteamento, excluindo valores de parâmetro de roteamento interpolados. |
retorna | Component|\Promise<Component>|undefined | Retorna um componente ou uma promise que é resolvida para um componente. |
Se onmatch
retornar um componente ou uma promise que é resolvida para um componente, este componente é usado como o vnode.tag
para o primeiro argumento no método render
do RouteResolver. Caso contrário, vnode.tag
é definido como "div"
. Da mesma forma, se o método onmatch
for omitido, vnode.tag
também é "div"
.
Se onmatch
retornar uma Promise que for rejeitada, o roteador redirecionará para a rota definida em defaultRoute
. Você pode substituir este comportamento chamando .catch
na cadeia de promise antes de retorná-la.
routeResolver.render
O método render
é chamado em cada redesenho para uma rota correspondente. É similar ao método view
dos componentes e tem como objetivo simplificar a composição de componentes. Ele também permite que você escape do comportamento normal do Mithril.js de substituir toda a subárvore.
vnode = routeResolver.render(vnode)
Parâmetro | Tipo | Descrição |
---|---|---|
vnode | Object | Um vnode cujo objeto de atributos contém parâmetros de roteamento. Se onmatch não retornar um componente ou uma promise que é resolvida para um componente, o campo tag do vnode assume o padrão "div" . |
vnode.attrs | Object | Um mapa de valores de parâmetro de URL. |
retorna | Array<Vnode>|Vnode | Os vnodes a serem renderizados. |
O parâmetro vnode
é equivalente a m(Component, m.route.param())
, onde Component
é o componente resolvido para a rota (após routeResolver.onmatch
) e m.route.param()
é como documentado aqui. Se você omitir este método, o valor de retorno padrão é [vnode]
, embrulhado em um fragmento para que você possa usar parâmetros de chave. Combinado com um parâmetro :key
, ele se torna um fragmento chaveado de elemento único, já que acaba renderizando para algo como [m(Component, {key: m.route.param("key"), ...})]
.
Como funciona
O sistema de roteamento permite a criação de Single Page Applications (SPAs), que são aplicações capazes de navegar entre "páginas" sem a necessidade de recarregar o navegador.
Ele permite a navegabilidade perfeita, preservando a capacidade de marcar cada página individualmente e a capacidade de navegar no aplicativo por meio do mecanismo de histórico do navegador.
O roteamento sem atualizações de página é parcialmente possível pela API history.pushState
. Usando esta API, é possível alterar programaticamente o URL exibido pelo navegador após o carregamento de uma página, mas é responsabilidade do desenvolvedor do aplicativo garantir que a navegação para qualquer URL fornecido a partir de um estado inativo (por exemplo, uma nova aba) renderize a marcação apropriada.
Estratégias de roteamento
A estratégia de roteamento define como uma biblioteca implementa o roteamento. Existem três estratégias gerais que podem ser usadas para implementar um sistema de roteamento SPA, e cada uma tem diferentes ressalvas:
m.route.prefix = '#!'
(padrão) – Usando a porção identificador de fragmento (também conhecido como hash) da URL. Uma URL usando esta estratégia normalmente se parece comhttps://localhost/#!/page1
.m.route.prefix = '?'
– Usando a querystring. Uma URL usando esta estratégia normalmente se parece comhttps://localhost/?/page1
.m.route.prefix = ''
– Usando o nome do caminho. Uma URL usando esta estratégia normalmente se parece comhttps://localhost/page1
.
A estratégia de hash tem garantia de funcionar em navegadores que não oferecem suporte à API history.pushState
, pois utiliza o evento onhashchange
como alternativa. Use esta estratégia se você quiser manter os hashes puramente locais.
A estratégia de querystring permite a detecção do lado do servidor, mas não aparece como um caminho normal. Use esta estratégia se você quiser suportar e potencialmente detectar links ancorados no lado do servidor, e você não for capaz de fazer as alterações necessárias para suportar a estratégia de nome do caminho (como se você estiver usando o Apache e não puder modificar seu .htaccess).
A estratégia de nome do caminho produz URLs mais limpas, mas requer configuração do servidor para servir o código do aplicativo de página única em cada URL que o aplicativo pode rotear. Use esta estratégia se você quiser URLs de aparência mais limpa.
Aplicativos de página única que usam a estratégia de hash geralmente usam a convenção de ter um ponto de exclamação após o hash para indicar que eles estão usando o hash como um mecanismo de roteamento e não para fins de vinculação a âncoras. A string #!
é conhecida como hashbang.
A estratégia padrão usa o hashbang.
Uso típico
Geralmente, é necessário criar alguns componentes para associar às rotas:
var Home = {
view: function () {
return [m(Menu), m('h1', 'Home')];
},
};
var Page1 = {
view: function () {
return [m(Menu), m('h1', 'Page 1')];
},
};
No exemplo acima, há dois componentes: Home
e Page1
. Cada um contém um menu e algum texto. O menu em si está sendo definido como um componente para evitar repetição:
var Menu = {
view: function () {
return m('nav', [
m(m.route.Link, { href: '/' }, 'Home'),
m(m.route.Link, { href: '/page1' }, 'Page 1'),
]);
},
};
Agora podemos definir rotas e mapear nossos componentes para elas:
m.route(document.body, '/', {
'/': Home,
'/page1': Page1,
});
Aqui especificamos duas rotas: /
e /page1
, que renderizam seus respectivos componentes quando o usuário navega para cada URL.
Navegando para diferentes rotas
No exemplo acima, o componente Menu
tem dois m.route.Link
s. Isso cria um elemento, que por padrão é um <a>
, e o configura para que, ao ser clicado, o usuário navegue para outra rota. A navegação ocorre apenas localmente, não remotamente.
Você também pode navegar programaticamente, via m.route.set(route)
. Por exemplo, m.route.set("/page1")
.
Ao navegar entre rotas, o prefixo do roteador é tratado automaticamente. Em outras palavras, omita o hashbang #!
(ou qualquer prefixo que você definir para m.route.prefix
) ao vincular rotas Mithril.js, incluindo em m.route.set
e em m.route.Link
.
Note que, ao navegar entre componentes, toda a subárvore é substituída. Use um resolvedor de rota com um método render
se você quiser apenas atualizar uma parte da subárvore.
Parâmetros de roteamento
Às vezes, queremos ter um ID variável em uma rota, mas não queremos especificar uma rota separada para cada ID possível. Para isso, o Mithril.js oferece suporte a rotas parametrizadas.
var Edit = {
view: function (vnode) {
return [m(Menu), m('h1', 'Editing ' + vnode.attrs.id)];
},
};
m.route(document.body, '/edit/1', {
'/edit/:id': Edit,
});
No exemplo acima, definimos uma rota /edit/:id
. Isso cria uma rota dinâmica que corresponde a qualquer URL começando com /edit/
seguido por dados. O valor id
é então mapeado como um atributo do vnode do componente (vnode.attrs.id
).
É possível ter vários argumentos em uma rota, por exemplo, /edit/:projectID/:userID
renderizaria as propriedades projectID
e userID
no objeto de atributos vnode do componente.
Parâmetro chave
Quando um usuário navega de uma rota parametrizada para a mesma rota com um parâmetro diferente (por exemplo, indo de /page/1
para /page/2
dada uma rota /page/:id
), o componente não seria recriado do zero, pois ambas as rotas são resolvidas para o mesmo componente e, portanto, resultam em um diff virtual DOM no local. Isso tem como efeito colateral disparar a função onupdate
, em vez de oninit
/oncreate
. No entanto, é relativamente comum que um desenvolvedor queira sincronizar a recriação do componente com o evento de mudança de rota.
Para conseguir isso, é possível combinar a parametrização de rota com keys para um padrão muito conveniente:
m.route(document.body, '/edit/1', {
'/edit/:key': Edit,
});
Isso significa que o vnode que é criado para o componente raiz da rota tem um objeto de parâmetro de rota key
. Os parâmetros de rota se tornam attrs
no vnode. Assim, ao pular de uma página para outra, a key
muda e faz com que o componente seja recriado do zero (já que a chave (key) informa ao mecanismo de DOM virtual que os componentes antigo e novo são entidades distintas).
Você pode levar essa ideia adiante para criar componentes que se recriam quando recarregados:
m.route.set(m.route.get(), {key: Date.now()})
Ou até mesmo usar o recurso history state
para conseguir componentes recarregáveis sem poluir a URL:
m.route.set(m.route.get(), null, {state: {key: Date.now()}})
Observe que o parâmetro chave funciona apenas para rotas de componente. Se você estiver usando um resolvedor de rota, você precisará usar um fragmento chaveado de filho único, passando key: m.route.param("key")
, para realizar o mesmo.
Rotas variádicas
Também é possível ter rotas variádicas, ou seja, uma rota com um argumento que contém nomes de caminho de URL que contêm barras:
m.route(document.body, '/edit/pictures/image.jpg', {
'/edit/:file...': Edit,
});
Tratamento de 404s
Para aplicativos JavaScript isomórficos / universais, um parâmetro de URL e uma rota variádica combinados são muito úteis para exibir uma página de erro 404 personalizada.
Em um caso de erro 404 Não Encontrado, o servidor envia de volta a página personalizada para o cliente. Quando o Mithril.js é carregado, ele redirecionará o cliente para a rota padrão porque não pode saber dessa rota.
m.route(document.body, '/', {
'/': homeComponent,
// [...]
'/:404...': errorPageComponent,
});
History state
É possível aproveitar a API history.pushState
para melhorar a experiência de navegação do usuário. Por exemplo, um aplicativo pode "lembrar" o estado de um formulário grande quando o usuário sai de uma página navegando para fora, de modo que, se o usuário pressionar o botão Voltar no navegador, ele terá o formulário preenchido em vez de um formulário em branco.
Por exemplo, você pode criar um formulário como este:
var state = {
term: '',
search: function () {
// save the state for this route
// isso equivale a `history.replaceState({term: state.term}, '', location.href)`
m.route.set(m.route.get(), null, {
replace: true,
state: { term: state.term },
});
// navigate away
location.href = 'https://google.com/?q=' + state.term;
},
};
var Form = {
oninit: function (vnode) {
state.term = vnode.attrs.term || ''; // populated from the `history.state` property if the user presses the back button
},
view: function () {
return m('form', [
m("input[placeholder='Search']", {
oninput: function (e) {
state.term = e.target.value;
},
value: state.term,
}),
m('button', { onclick: state.search }, 'Search'),
]);
},
};
m.route(document.body, '/', {
'/': Form,
});
Assim, se o usuário pesquisar e pressionar o botão Voltar, o campo de entrada ainda estará preenchido com o termo de pesquisa. Esta técnica pode melhorar a experiência do usuário de formulários grandes e outros aplicativos onde o estado não persistido é trabalhoso para um usuário produzir.
Alterando o prefixo do roteador
O prefixo do roteador é um fragmento da URL que determina a estratégia subjacente usada pelo roteador.
// set to pathname strategy
m.route.prefix = '';
// set to querystring strategy
m.route.prefix = '?';
// set to hash without bang
m.route.prefix = '#';
// set to pathname strategy on a non-root URL
// e.g. if the app lives under `https://localhost/my-app` and something else
// lives under `https://localhost`
m.route.prefix = '/my-app';
Resolução Avançada de Componentes
Em vez de associar um componente diretamente a uma rota, você pode especificar um objeto RouteResolver
. Um objeto RouteResolver
contém um método onmatch()
e um método render()
. Ambos os métodos são opcionais, mas pelo menos um deles deve estar presente.
m.route(document.body, '/', {
'/': {
onmatch: function (args, requestedPath, route) {
return Home;
},
render: function (vnode) {
return vnode; // equivalente a m(Home)
},
},
});
RouteResolvers
são úteis para implementar diversos casos de uso avançados de roteamento.
Envolvendo um Componente de Layout
Frequentemente, é desejável envolver todos ou a maioria dos componentes roteados em uma estrutura reutilizável (geralmente chamada de "layout"). Para isso, você precisa primeiro criar um componente que contenha a marcação comum que envolverá os diferentes componentes:
var Layout = {
view: function (vnode) {
return m('.layout', vnode.children);
},
};
No exemplo acima, o layout consiste apenas em uma <div class="layout">
que contém os filhos passados para o componente, mas em um cenário real, poderia ser tão complexo quanto necessário.
Uma maneira de envolver o layout é definir um componente anônimo no mapa de rotas:
// example 1
m.route(document.body, '/', {
'/': {
view: function () {
return m(Layout, m(Home));
},
},
'/form': {
view: function () {
return m(Layout, m(Form));
},
},
});
No entanto, observe que, como o componente de nível superior é anônimo, navegar da rota /
para a rota /form
(ou vice-versa) irá desmontar o componente anônimo e recriar o DOM do zero. Se o componente Layout
tivesse métodos de ciclo de vida definidos, os hooks oninit
e oncreate
seriam disparados a cada mudança de rota. Dependendo da aplicação, isso pode ou não ser desejável.
Se você preferir que o componente Layout
seja atualizado e mantido intacto, em vez de ser recriado do zero, você deve usar um RouteResolver
como o objeto raiz:
// example 2
m.route(document.body, '/', {
'/': {
render: function () {
return m(Layout, m(Home));
},
},
'/form': {
render: function () {
return m(Layout, m(Form));
},
},
});
Observe que, neste caso, se o componente Layout
tiver métodos de ciclo de vida oninit
e oncreate
, eles só serão disparados na primeira mudança de rota (assumindo que todas as rotas usem o mesmo layout).
Para ilustrar a diferença entre os dois exemplos, o exemplo 1 equivale a este código:
// functionally equivalent to example 1
var Anon1 = {
view: function () {
return m(Layout, m(Home));
},
};
var Anon2 = {
view: function () {
return m(Layout, m(Form));
},
};
m.route(document.body, '/', {
'/': {
render: function () {
return m(Anon1);
},
},
'/form': {
render: function () {
return m(Anon2);
},
},
});
Como Anon1
e Anon2
são componentes diferentes, suas subárvores (incluindo Layout
) são recriadas do zero. Isso também é o que acontece quando os componentes são usados diretamente sem um RouteResolver
.
No exemplo 2, como Layout
é o componente de nível superior em ambas as rotas, o DOM para o componente Layout
é atualizado (ou seja, deixado intacto se não houver alterações), e apenas a mudança de Home
para Form
aciona uma recriação dessa subseção do DOM.
Redirecionamento
O hook onmatch
do RouteResolver
pode ser usado para executar lógica antes que o componente de nível superior de uma rota seja inicializado. Você pode usar m.route.set()
do Mithril ou a API history
nativa do HTML5. Ao redirecionar com a API history
, o hook onmatch
deve retornar uma Promise
que nunca se resolve, para evitar a resolução da rota correspondente. m.route.set()
cancela a resolução da rota correspondente internamente, portanto, isso não é necessário com ele.
Exemplo: Autenticação
O exemplo abaixo demonstra como implementar um mecanismo de login que impede que os usuários vejam a página /secret
, a menos que estejam logados.
var isLoggedIn = false;
var Login = {
view: function () {
return m('form', [
m(
'button[type=button]',
{
onclick: function () {
isLoggedIn = true;
m.route.set('/secret');
},
},
'Login'
),
]);
},
};
m.route(document.body, '/secret', {
'/secret': {
onmatch: function () {
if (!isLoggedIn) m.route.set('/login');
else return Home;
},
},
'/login': Login,
});
Quando a aplicação carrega, onmatch
é chamado e, como isLoggedIn
é falso, a aplicação redireciona para /login
. Depois que o usuário clicar no botão de login, isLoggedIn
será definido como verdadeiro e a aplicação redirecionará para /secret
. O hook onmatch
será executado novamente e, como isLoggedIn
está definido como verdadeiro, a aplicação renderizará o componente Home
.
Para simplificar, no exemplo acima, o status de login do usuário é armazenado em uma variável global, e esse sinalizador é simplesmente alternado quando o usuário clica no botão de login. Em uma aplicação real, um usuário obviamente precisaria fornecer credenciais de login válidas, e clicar no botão de login acionaria uma requisição a um servidor para autenticar o usuário:
var Auth = {
username: '',
password: '',
setUsername: function (value) {
Auth.username = value;
},
setPassword: function (value) {
Auth.password = value;
},
login: function () {
m.request({
url: '/api/v1/auth',
params: { username: Auth.username, password: Auth.password },
}).then(function (data) {
localStorage.setItem('auth-token', data.token);
m.route.set('/secret');
});
},
};
var Login = {
view: function () {
return m('form', [
m('input[type=text]', {
oninput: function (e) {
Auth.setUsername(e.target.value);
},
value: Auth.username,
}),
m('input[type=password]', {
oninput: function (e) {
Auth.setPassword(e.target.value);
},
value: Auth.password,
}),
m('button[type=button]', { onclick: Auth.login }, 'Login'),
]);
},
};
m.route(document.body, '/secret', {
'/secret': {
onmatch: function () {
if (!localStorage.getItem('auth-token')) m.route.set('/login');
else return Home;
},
},
'/login': Login,
});
Pré-carregamento de Dados
Normalmente, um componente pode carregar dados durante a inicialização. Carregar dados dessa forma faz com que o componente seja renderizado duas vezes. A primeira renderização ocorre durante o roteamento, e a segunda é disparada após a conclusão da requisição. Tome cuidado para observar que loadUsers()
retorna uma Promise
, mas qualquer Promise
retornada por oninit
é atualmente ignorada. A segunda passagem de renderização vem da opção background
para m.request
.
var state = {
users: [],
loadUsers: function () {
return m.request('/api/v1/users').then(function (users) {
state.users = users;
});
},
};
m.route(document.body, '/user/list', {
'/user/list': {
oninit: state.loadUsers,
view: function () {
return state.users.length > 0
? state.users.map(function (user) {
return m('div', user.id);
})
: 'loading';
},
},
});
No exemplo acima, na primeira renderização, a UI exibe "loading"
já que state.users
é um array vazio antes que a solicitação seja concluída. Então, uma vez que os dados estão disponíveis, a UI redesenha e uma lista de IDs de usuário é mostrada.
RouteResolvers
podem ser usados como um mecanismo para pré-carregar dados antes de renderizar um componente, a fim de evitar oscilações na interface do usuário (UI) e, assim, evitar a necessidade de um indicador de carregamento:
var state = {
users: [],
loadUsers: function () {
return m.request('/api/v1/users').then(function (users) {
state.users = users;
});
},
};
m.route(document.body, '/user/list', {
'/user/list': {
onmatch: state.loadUsers,
render: function () {
return state.users.map(function (user) {
return m('div', user.id);
});
},
},
});
Acima, render
só é executado após a conclusão da solicitação, tornando o operador ternário redundante.
Divisão de Código (Code Splitting)
Em uma aplicação grande, pode ser desejável baixar o código para cada rota sob demanda, em vez de tudo de uma vez. Dividir a base de código dessa forma é conhecido como divisão de código (code splitting) ou carregamento lento (lazy loading). Em Mithril.js, isso pode ser realizado retornando uma Promise
do hook onmatch
:
Na sua forma mais básica, pode-se fazer o seguinte:
// Home.js
module.export = {
view: function () {
return [m(Menu), m('h1', 'Home')];
},
};
// index.js
function load(file) {
return m.request({
method: 'GET',
url: file,
extract: function (xhr) {
return new Function(
'var module = {};' + xhr.responseText + ';return module.exports;'
);
},
});
}
m.route(document.body, '/', {
'/': {
onmatch: function () {
return load('Home.js');
},
},
});
No entanto, para que isso funcione em um ambiente de produção, seria necessário empacotar todas as dependências do módulo Home.js
em um único arquivo, que seria servido pelo servidor.
Felizmente, existem várias ferramentas que facilitam a tarefa de agrupar módulos para carregamento lento. Aqui está um exemplo usando import(...)
dinâmico nativo, suportado por muitos bundlers:
m.route(document.body, '/', {
'/': {
onmatch: function () {
return import('./Home.js');
},
},
});
Rotas Tipadas (Typed Routes)
Em certos casos avançados de roteamento, você pode querer restringir um valor além do próprio caminho, permitindo apenas valores como um ID numérico. Você pode fazer isso facilmente retornando m.route.SKIP
de uma rota.
m.route(document.body, '/', {
'/view/:id': {
onmatch: function (args) {
if (!/^\d+$/.test(args.id)) return m.route.SKIP;
return ItemView;
},
},
'/view/:name': UserView,
});
Rotas Ocultas (Hidden Routes)
Em raras circunstâncias, você pode querer ocultar certas rotas de alguns usuários. Por exemplo, um usuário pode não ter permissão para visualizar um determinado perfil e, em vez de mostrar um erro de permissão, você preferiria simular que a rota não existe e redirecionar para uma página 404. Nesse caso, você pode usar m.route.SKIP
para simular que a rota não existe.
m.route(document.body, '/', {
'/user/:id': {
onmatch: function (args) {
return Model.checkViewable(args.id).then(function (viewable) {
return viewable ? UserView : m.route.SKIP;
});
},
},
'/:404...': PageNotFound,
});
Cancelamento / Bloqueio de Rota
O RouteResolver
onmatch
pode impedir a resolução da rota retornando uma Promise
que nunca se resolve. Isso pode ser usado para detectar tentativas de resolução de rotas redundantes e cancelá-las:
m.route(document.body, '/', {
'/': {
onmatch: function (args, requestedPath) {
if (m.route.get() === requestedPath) return new Promise(function () {});
},
},
});
Integração de Terceiros
Em certas situações, você pode precisar integrar o Mithril.js com outro framework, como o React. Veja como fazer isso:
- Defina todas as suas rotas usando
m.route
normalmente, mas certifique-se de usá-lo apenas uma vez por aplicação. Não é possível definir múltiplos pontos de roteamento. - Quando você precisar remover as subscrições de roteamento, use
m.mount(root, null)
, utilizando a mesma raiz que você utilizou emm.route(root, ...)
.m.route
utilizam.mount
internamente para realizar a configuração, portanto, não há mágica envolvida.
Aqui está um exemplo com React:
class Child extends React.Component {
constructor(props) {
super(props);
this.root = React.createRef();
}
componentDidMount() {
m.route(this.root, '/', {
// ...
});
}
componentDidUnmount() {
m.mount(this.root, null);
}
render() {
return <div ref={this.root} />;
}
}
E aqui está a versão equivalente com Vue:
<div ref="root"></div>
Vue.component('my-child', {
template: `<div ref="root"></div>`,
mounted: function () {
m.route(this.$refs.root, '/', {
// ...
});
},
destroyed: function () {
m.mount(this.$refs.root, null);
},
});