route(root, defaultRoute, routes)
Descripción
Permite la navegación entre "páginas" dentro de una aplicación web.
var Home = {
view: function () {
return 'Bienvenida';
},
};
m.route(document.body, '/home', {
'/home': Home, // define `https://localhost/#!/home`
});
Solo se permite una llamada a m.route
por aplicación.
Firma
m.route(root, defaultRoute, routes)
Argumento | Tipo | Obligatorio | Descripción |
---|---|---|---|
root | Element | Sí | Un elemento DOM que será el nodo padre del subárbol renderizado por el enrutador. |
defaultRoute | String | Sí | La ruta a la que se redirigirá si la URL actual no coincide con ninguna de las rutas definidas. Nota: esta no es la ruta inicial que se carga al inicio de la aplicación. La ruta inicial será la URL presente en la barra de direcciones del navegador. |
routes | Object<String,Component|RouteResolver> | Sí | Un objeto cuyas claves son cadenas de ruta (URLs) y los valores son componentes o un RouteResolver. |
devuelve | Devuelve undefined |
Miembros estáticos
m.route.set
Redirige a una ruta coincidente, o a la ruta predeterminada si no se encuentra ninguna. Provoca un redibujo asíncrono en todos los puntos de montaje de Mithril.
m.route.set(path, params, options)
Argumento | Tipo | Obligatorio | Descripción |
---|---|---|---|
path | String | Sí | El nombre de la ruta a la que se va a dirigir, sin el prefijo del enrutador (por ejemplo, sin #!/ ). La ruta puede incluir parámetros, que se interpolan con los valores de params . |
params | Object | No | Parámetros de enrutamiento. Si path contiene marcadores para parámetros de enrutamiento (por ejemplo, :id ), las propiedades de este objeto se interpolan en la cadena de ruta. |
options.replace | Boolean | No | Indica si se debe crear una nueva entrada en el historial del navegador o reemplazar la actual. El valor predeterminado es false . |
options.state | Object | No | El objeto state para pasar a la llamada subyacente history.pushState / history.replaceState . Este objeto de estado está disponible en la propiedad history.state y se fusiona en el objeto de parámetros de enrutamiento. Ten en cuenta que esta opción solo funciona con la API pushState y se ignora si el enrutador recurre al modo hashchange (es decir, si la API pushState no está disponible). |
options.title | String | No | La cadena title para pasar a la llamada subyacente history.pushState / history.replaceState . |
devuelve | Devuelve undefined |
Recuerda que al usar .set
con params
también necesitas definir la ruta en la configuración inicial del enrutador:
var Article = {
view: function (vnode) {
return 'Este es el artículo ' + vnode.attrs.articleid;
},
};
m.route(document.body, '/article/1', {
'/article/:articleid': Article,
});
m.route.set('/article/:articleid', { articleid: 1 });
m.route.get
Devuelve la última ruta completamente resuelta, sin el prefijo del enrutador. Puede diferir de la ruta que se muestra en la barra de ubicación del navegador mientras una ruta asíncrona está pendiente de resolución.
path = m.route.get()
Argumento | Tipo | Obligatorio | Descripción |
---|---|---|---|
devuelve | String | Devuelve la última ruta completamente resuelta. |
m.route.prefix
Define el prefijo del enrutador. El prefijo del enrutador es un fragmento de la URL que dicta la estrategia subyacente utilizada por el enrutador.
m.route.prefix = prefix
Argumento | Tipo | Obligatorio | Descripción |
---|---|---|---|
prefix | String | Sí | El prefijo que controla la estrategia de enrutamiento subyacente utilizada por Mithril. |
Esta es una propiedad simple, por lo que puedes tanto leerla como escribirla.
m.route.Link
Su función principal es crear enlaces <a>
dinámicos, modificando los atributos href
locales para que coincidan con el prefijo de ruta.
m(m.route.Link, { href: '/foo' }, 'foo');
// A menos que m.route.prefix haya cambiado de la estrategia predeterminada, se renderiza a:
// <a href="#!/foo">foo</a>
Los enlaces aceptan una selección de atributos especiales:
selector
es lo que se pasaría como el primer argumento am
: cualquier selector es válido, incluidos los elementos que no sona
.params
yoptions
son los argumentos con los mismos nombres que se definen enm.route.set
.disabled
, si es verdadero, deshabilita el comportamiento de enrutamiento y cualquier controladoronclick
asociado, y adjunta un atributodata-disabled="true"
para sugerencias de accesibilidad; si el elemento es una
, se elimina elhref
.
Para evitar el comportamiento de enrutamiento, no uses la API de manejo de eventos; usa la propiedad disabled
.
m(
m.route.Link,
{
href: '/foo',
selector: 'button.large',
disabled: true,
params: { key: 'value' },
options: { replace: true },
},
'nombre del enlace'
);
// Se renderiza a:
// <button disabled aria-disabled="true" class="large">nombre del enlace</button>
vnode = m(m.route.Link, attributes, children)
Argumento | Tipo | Obligatorio | Descripción |
---|---|---|---|
attributes.href | Object | Sí | La ruta de destino a la que se va a navegar. |
attributes.disabled | Boolean | No | Deshabilita el elemento de forma accesible. |
attributes.selector | String|Object|Function | No | Un selector para m , el valor predeterminado es "a" . |
attributes.options | Object | No | Establece las options pasadas a m.route.set . |
attributes.params | Object | No | Establece los params pasados a m.route.set . |
attributes | Object | No | Cualquier otro atributo que se reenvíe a m . |
children | Array<Vnode>|String|Number|Boolean | No | vnodes secundarios para este enlace. |
devuelve | Vnode | Un vnode. |
m.route.param
Obtiene un parámetro de ruta de la última ruta completamente resuelta. Un parámetro de ruta es un par clave-valor. Los parámetros de ruta pueden provenir de diferentes lugares:
- interpolaciones de ruta (por ejemplo, si una ruta es
/users/:id
y se resuelve a/users/1
, el parámetro de ruta tiene una claveid
y el valor"1"
) - cadenas de consulta del enrutador (por ejemplo, si la ruta es
/users?page=1
, el parámetro de ruta tiene una clavepage
y el valor"1"
) history.state
(por ejemplo, sihistory.state
es{foo: "bar"}
, el parámetro de ruta tiene la clavefoo
y el valor"bar"
)
value = m.route.param(key)
Argumento | Tipo | Obligatorio | Descripción |
---|---|---|---|
key | String | No | El nombre del parámetro de ruta que se desea obtener (por ejemplo, id en la ruta /users/:id , o page en la ruta /users/1?page=3 , o una clave en history.state ). Si no se especifica una clave, devuelve un objeto que contiene todas las claves de interpolación. |
devuelve | String|Object | Devuelve el valor para la clave especificada. Si no se especifica una clave, devuelve un objeto que contiene todos los parámetros de ruta. |
Ten en cuenta que en la función onmatch
de un RouteResolver, la nueva ruta aún no se ha resuelto por completo y m.route.param()
devolverá los parámetros de la ruta anterior, si los hay. onmatch
recibe los parámetros de la nueva ruta como un argumento.
m.route.SKIP
Un valor especial que se puede devolver desde el onmatch
de un resolvedor de rutas para saltar a la siguiente ruta definida en la configuración del enrutador.
RouteResolver
Un RouteResolver
es un objeto que no es un componente y que contiene un método onmatch
y/o un método render
. Ambos métodos son opcionales, pero al menos uno debe estar presente.
Si un objeto se puede detectar como un componente (por la presencia de un método view
o por ser una function
/class
), se tratará como tal incluso si tiene métodos onmatch
o render
. Dado que un RouteResolver
no es un componente, no tiene métodos de ciclo de vida.
Como regla general, los RouteResolvers
deben estar en el mismo archivo que la llamada m.route
, mientras que las definiciones de componentes deben estar en sus propios módulos.
routeResolver = {onmatch, render}
Cuando se utilizan componentes, se puede pensar en ellos como una forma abreviada de este resolvedor de rutas. Por ejemplo, si tu componente es Home
:
var routeResolver = {
onmatch: function () {
return Home;
},
render: function (vnode) {
return [vnode];
},
};
routeResolver.onmatch
El hook onmatch
se ejecuta cuando el enrutador necesita encontrar un componente para renderizar. Se llama una vez por cada cambio de ruta del enrutador, pero no en los repintados posteriores mientras se está en la misma ruta. Se puede utilizar para ejecutar lógica antes de que se inicialice un componente (por ejemplo, lógica de autenticación, precarga de datos, seguimiento de análisis de redirección, etc.).
Este método también te permite definir de forma asíncrona qué componente se renderizará, lo que lo hace adecuado para la división de código y la carga asíncrona de módulos. Para renderizar un componente de forma asíncrona, devuelve una promesa que se resuelve en un componente.
Para obtener más información sobre onmatch
, consulta la sección resolución avanzada de componentes
routeResolver.onmatch(args, requestedPath, route)
Argumento | Tipo | Descripción |
---|---|---|
args | Object | Los parámetros de enrutamiento extraídos de la URL. |
requestedPath | String | La ruta solicitada por la última acción de enrutamiento, incluidos los valores de los parámetros de enrutamiento interpolados, pero sin el prefijo del enrutador. Cuando se llama a onmatch , la resolución para esta ruta no está completa y m.route.get() todavía devuelve la ruta anterior. |
route | String | La ruta solicitada por la última acción de enrutamiento, excluyendo los valores de los parámetros de enrutamiento interpolados. |
devuelve | Component|\Promise<Component>|undefined | Devuelve un componente, una promesa que se resuelve en un componente, o undefined . |
Si onmatch
devuelve un componente o una promesa que se resuelve en un componente, este componente se utiliza como el vnode.tag
para el primer argumento en el método render
del RouteResolver
. De lo contrario, el campo tag
del vnode se establece por defecto en "div"
. Del mismo modo, si se omite el método onmatch
, vnode.tag
también es "div"
.
Si onmatch
devuelve una promesa que se rechaza, el enrutador redirige de nuevo a defaultRoute
. Puedes anular este comportamiento llamando a .catch
en la cadena de promesas antes de devolverla.
routeResolver.render
El método render
se llama en cada repintado para una ruta coincidente. Es similar al método view
en los componentes y existe para simplificar la composición de componentes. También te permite escapar del comportamiento normal de Mithril.js de reemplazar todo el subárbol.
vnode = routeResolver.render(vnode)
Argumento | Tipo | Descripción |
---|---|---|
vnode | Object | Un vnode cuyo objeto de atributos contiene parámetros de enrutamiento. Si onmatch no devuelve un componente o una promesa que se resuelve en un componente, el campo tag del vnode toma el valor predeterminado "div" |
vnode.attrs | Object | Un mapa de valores de parámetros de URL |
devuelve | Array<Vnode>|Vnode | Los vnodes que se van a repintar |
El parámetro vnode
es simplemente m(Component, m.route.param())
donde Component
es el componente resuelto para la ruta (después de routeResolver.onmatch
) y m.route.param()
es como se documenta aquí. Si omites este método, el valor de retorno predeterminado es [vnode]
, envuelto en un fragmento para que puedas utilizar parámetros de clave. Combinado con un parámetro :key
, se convierte en un fragmento con clave de un solo elemento, ya que termina repintando algo como [m(Component, {key: m.route.param("key"), ...})]
.
Cómo funciona
El enrutamiento es un sistema que permite crear aplicaciones de una sola página (SPA), es decir, aplicaciones que pueden ir de una "página" a otra sin provocar una actualización completa del navegador.
Permite una navegabilidad fluida al tiempo que preserva la capacidad de marcar cada página individualmente y la capacidad de navegar por la aplicación a través del mecanismo de historial del navegador.
El enrutamiento sin refrescar la página es posible gracias a la API subyacente history.pushState
. El uso de esta API permite cambiar programáticamente la URL que muestra el navegador después de que se haya cargado una página, pero es responsabilidad del desarrollador de la aplicación asegurarse de que la navegación a cualquier URL dada desde un estado frío (por ejemplo, una nueva pestaña) renderice el marcado apropiado.
Estrategias de enrutamiento
La estrategia de enrutamiento dicta cómo una biblioteca implementa el enrutamiento. Hay tres estrategias generales que se pueden utilizar para implementar un sistema de enrutamiento SPA, y cada una tiene diferentes consideraciones:
m.route.prefix = '#!'
(predeterminado) – Utilizando la porción del identificador de fragmento (también conocido como hash) de la URL. Una URL que utiliza esta estrategia normalmente se ve así:https://localhost/#!/page1
m.route.prefix = '?'
– Utilizando la cadena de consulta. Una URL que utiliza esta estrategia normalmente se ve así:https://localhost/?/page1
m.route.prefix = ''
– Utilizando el nombre de la ruta. Una URL que utiliza esta estrategia normalmente se ve así:https://localhost/page1
La estrategia hash está garantizada que funcionará en los navegadores que no admiten history.pushState
, porque puede recurrir al uso de onhashchange
. Utiliza esta estrategia si quieres mantener los hashes puramente locales.
La estrategia de cadena de consulta permite la detección del lado del servidor, pero no aparece como una ruta normal. Utiliza esta estrategia si quieres admitir y potencialmente detectar enlaces anclados del lado del servidor y no puedes realizar los cambios necesarios para admitir la estrategia de nombre de ruta (como si estás utilizando Apache y no puedes modificar tu .htaccess).
La estrategia de nombre de ruta produce las URL de aspecto más limpio, pero requiere configurar el servidor para que sirva el código de la aplicación de una sola página desde cada URL a la que la aplicación puede enrutar. Utiliza esta estrategia si quieres URL de aspecto más limpio.
Las aplicaciones de una sola página que utilizan la estrategia hash a menudo utilizan la convención de tener un signo de exclamación después del hash para indicar que están utilizando el hash como un mecanismo de enrutamiento y no con el propósito de enlazar a anclajes. La cadena #!
se denomina hashbang.
La estrategia predeterminada utiliza el hashbang.
Uso típico
Normalmente, necesitas crear algunos componentes para asignar rutas a:
var Home = {
view: function () {
return [m(Menu), m('h1', 'Inicio')];
},
};
var Page1 = {
view: function () {
return [m(Menu), m('h1', 'Página 1')];
},
};
En el ejemplo anterior, hay dos componentes: Home
y Page1
. Cada uno contiene un menú y algo de texto. El menú en sí se define como un componente para evitar la repetición:
var Menu = {
view: function () {
return m('nav', [
m(m.route.Link, { href: '/' }, 'Inicio'),
m(m.route.Link, { href: '/page1' }, 'Página 1'),
]);
},
};
Ahora podemos definir rutas y asignar nuestros componentes a ellas:
m.route(document.body, '/', {
'/': Home,
'/page1': Page1,
});
Aquí especificamos dos rutas: /
y /page1
, que renderizan sus respectivos componentes cuando el usuario navega a cada URL.
Navegar a diferentes rutas
En el ejemplo anterior, el componente Menu
tiene dos m.route.Link
s. Eso crea un elemento, por defecto un <a>
, y lo configura para que, si el usuario hace clic en él, navegue a otra ruta de forma local. No navega de forma remota, solo dentro de la aplicación.
También puedes navegar programáticamente, a través de m.route.set(route)
. Por ejemplo, m.route.set("/page1")
.
Al navegar entre rutas, el prefijo del enrutador se maneja automáticamente. En otras palabras, omite el hashbang #!
(o cualquier prefijo que establezcas en m.route.prefix
) al enlazar rutas de Mithril.js, tanto en m.route.set
como en m.route.Link
.
Ten en cuenta que al navegar entre componentes, se reemplaza todo el subárbol. Utiliza un resolvedor de rutas con un método render
si quieres simplemente actualizar el subárbol existente.
Parámetros de enrutamiento
A veces queremos que un id variable o información similar aparezcan en una ruta, pero no queremos especificar explícitamente una ruta separada para cada id posible. Para lograr eso, Mithril.js admite rutas parametrizadas:
var Edit = {
view: function (vnode) {
return [m(Menu), m('h1', 'Editando ' + vnode.attrs.id)];
},
};
m.route(document.body, '/edit/1', {
'/edit/:id': Edit,
});
En el ejemplo anterior, definimos una ruta /edit/:id
. Esto crea una ruta dinámica que coincide con cualquier URL que comience con /edit/
y sea seguida por algunos datos (por ejemplo, /edit/1
, edit/234
, etc.). El valor id
se asigna entonces como un atributo del vnode del componente (vnode.attrs.id
)
Es posible tener múltiples argumentos en una ruta, por ejemplo /edit/:projectID/:userID
produciría las propiedades projectID
y userID
en el objeto de atributos del vnode del componente.
Parámetro de clave
Cuando un usuario navega de una ruta parametrizada a la misma ruta con un parámetro diferente (por ejemplo, ir de /page/1
a /page/2
dada una ruta /page/:id
), el componente no se volvería a crear desde cero ya que ambas rutas se resuelven al mismo componente, y por lo tanto resultan en una actualización en el mismo lugar del DOM virtual. Esto tiene el efecto secundario de desencadenar el hook onupdate
, en lugar de oninit
/oncreate
. Sin embargo, es relativamente común que un desarrollador quiera sincronizar la recreación del componente con el evento de cambio de ruta.
Para lograr eso, es posible combinar la parametrización de la ruta con claves para un patrón muy conveniente:
m.route(document.body, '/edit/1', {
'/edit/:key': Edit,
});
Esto significa que el vnode que se crea para el componente raíz de la ruta tiene un objeto de parámetro de ruta clave
. Los parámetros de ruta se convierten en attrs
en el vnode. Por lo tanto, al saltar de una página a otra, la clave cambia y hace que el componente se vuelva a crear desde cero (ya que la clave le dice al motor DOM virtual que los componentes antiguos y nuevos son entidades diferentes).
Puedes llevar esa idea más allá para crear componentes que se recrean a sí mismos cuando se recargan:
m.route.set(m.route.get(), {key: Date.now()})
O incluso utilizar la función history state
para lograr componentes recargables sin contaminar la URL:
m.route.set(m.route.get(), null, {state: {key: Date.now()}})
Ten en cuenta que el parámetro clave solo funciona para rutas de componentes. Si estás utilizando un resolvedor de rutas, tendrás que utilizar un fragmento con clave de un solo hijo, pasando key: m.route.param("key")
, para lograr lo mismo.
Rutas variádicas
También es posible tener rutas variádicas, es decir, una ruta con un argumento que contiene nombres de ruta de URL que contienen barras diagonales:
m.route(document.body, '/edit/pictures/image.jpg', {
'/edit/:file...': Edit,
});
Manejo de 404s
Para una aplicación JavaScript isomórfica / universal, un parámetro de URL y una ruta variádica combinados son muy útiles para mostrar una página personalizada de error 404.
En caso de un error 404 No Encontrado, el servidor devuelve una página personalizada al cliente. Sin embargo, cuando Mithril.js se carga, redirige al cliente a la ruta predeterminada porque inicialmente no reconoce la ruta del error.
m.route(document.body, '/', {
'/': homeComponent,
// [...]
'/:404...': errorPageComponent,
});
History state
Es posible aprovechar al máximo la API history.pushState
subyacente para mejorar la experiencia de navegación del usuario. Por ejemplo, una aplicación podría "recordar" el estado de un formulario grande cuando el usuario abandona una página navegando fuera de ella, de modo que si el usuario presionara el botón de retroceso en el navegador, tendría el formulario lleno en lugar de un formulario en blanco.
Por ejemplo, podrías crear un formulario como este:
var state = {
term: '',
search: function () {
// guarda el estado para esta ruta
// esto es equivalente a `history.replaceState({term: state.term}, null, location.href)`
m.route.set(m.route.get(), null, {
replace: true,
state: { term: state.term },
});
// navegar fuera
location.href = 'https://google.com/?q=' + state.term;
},
};
var Form = {
oninit: function (vnode) {
state.term = vnode.attrs.term||''; // inicializado con el valor de la propiedad `history.state` si el usuario presiona el botón de retroceso
},
view: function () {
return m('form', [
m("input[placeholder='Buscar']", {
oninput: function (e) {
state.term = e.target.value;
},
value: state.term,
}),
m('button', { onclick: state.search }, 'Buscar'),
]);
},
};
m.route(document.body, '/', {
'/': Form,
});
De esta manera, si el usuario busca y presiona el botón de retroceso para volver a la aplicación, la entrada todavía estará poblada con el término de búsqueda. Esta técnica puede mejorar la experiencia del usuario de formularios grandes y otras aplicaciones donde el estado no persistente es laborioso para que un usuario lo produzca.
Cambiar el prefijo del enrutador
El prefijo del enrutador es un fragmento de la URL que dicta la estrategia subyacente utilizada por el enrutador.
// establecer la estrategia de nombre de ruta
m.route.prefix = '';
// establecer a la estrategia de cadena de consulta
m.route.prefix = '?';
// establecer a hash sin 'bang' (sin el signo de exclamación)
m.route.prefix = '#';
// establecer la estrategia de nombre de ruta en una URL que no sea la raíz
// por ejemplo, si la aplicación vive bajo `https://localhost/my-app` y algo más
// vive bajo `https://localhost`
m.route.prefix = '/my-app';
Resolución avanzada de componentes
En lugar de asignar un componente directamente a una ruta, puedes especificar un objeto RouteResolver
. Un objeto RouteResolver
contiene un método onmatch()
y/o un método render()
. Ambos métodos son opcionales, pero al menos uno de ellos debe estar presente.
m.route(document.body, '/', {
'/': {
onmatch: function (args, requestedPath, route) {
return Home;
},
render: function (vnode) {
return vnode; // equivalente a m(Home)
},
},
});
Los RouteResolvers
son útiles para implementar casos de enrutamiento avanzados.
Envolver un componente de diseño (layout)
A menudo es deseable envolver todos o la mayoría de los componentes enrutados en un contenedor reutilizable (a menudo llamado "layout"). Para ello, primero necesitas crear un componente que contenga el marcado común que envolverá los diferentes componentes:
var Layout = {
view: function (vnode) {
return m('.layout', vnode.children);
},
};
En el ejemplo anterior, el layout simplemente consiste en un <div class="layout">
que contiene los hijos pasados al componente, pero en un escenario real podría ser tan complejo como sea necesario.
Una forma de envolver el layout es definir un componente anónimo directamente en el mapa de rutas:
// ejemplo 1
m.route(document.body, '/', {
'/': {
view: function () {
return m(Layout, m(Home));
},
},
'/form': {
view: function () {
return m(Layout, m(Form));
},
},
});
Sin embargo, ten en cuenta que, dado que el componente de nivel superior es anónimo, al navegar de la ruta /
a la ruta /form
(o viceversa), el componente anónimo se desmontará y el DOM se recreará desde cero. Si el componente Layout
tuviera métodos de ciclo de vida definidos, los hooks oninit
y oncreate
se ejecutarían en cada cambio de ruta. Dependiendo de la aplicación, esto podría ser deseable o no.
Si prefieres que el componente Layout
se compare y se mantenga intacto en lugar de recrearse desde cero, debes usar un RouteResolver
como componente raíz:
// ejemplo 2
m.route(document.body, '/', {
'/': {
render: function () {
return m(Layout, m(Home));
},
},
'/form': {
render: function () {
return m(Layout, m(Form));
},
},
});
Ten en cuenta que en este caso, si el componente Layout
tiene métodos de ciclo de vida oninit
y oncreate
, solo se ejecutarían en el primer cambio de ruta (asumiendo que todas las rutas usan el mismo layout).
Para entender mejor la diferencia entre ambos ejemplos, el ejemplo 1 es funcionalmente equivalente a este código:
// funcionalmente equivalente al ejemplo 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);
},
},
});
Dado que Anon1
y Anon2
son componentes diferentes, sus subárboles (incluido Layout
) se recrean desde cero. Esto también es lo que sucede cuando los componentes se usan directamente sin un RouteResolver
.
En el ejemplo 2, dado que Layout
es el componente de nivel superior en ambas rutas, el DOM para el componente Layout
se compara (es decir, se deja intacto si no tiene cambios), y solo el cambio de Home
a Form
desencadena una recreación de esa subsección del DOM.
Redirección
El hook onmatch
de RouteResolver
se puede usar para ejecutar lógica antes de inicializar el componente de nivel superior en una ruta. Puedes usar m.route.set()
de Mithril o la API history
nativa de HTML5. Al redirigir con la API history
, el hook onmatch
debe devolver una promesa que nunca se resuelva para evitar que se complete la ruta coincidente. m.route.set()
cancela la resolución de la ruta coincidente internamente, por lo que esto no es necesario con ella.
Ejemplo: autenticación
El siguiente ejemplo muestra cómo implementar un muro de inicio de sesión que impide que los usuarios vean la página /secret
a menos que inicien sesión.
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,
});
Cuando se carga la aplicación, se llama a onmatch
y, dado que isLoggedIn
es falso, la aplicación se redirige a /login
. Una vez que el usuario presiona el botón de inicio de sesión, isLoggedIn
se establece en verdadero y la aplicación se redirige a /secret
. El hook onmatch
se ejecutaría una vez más, y dado que isLoggedIn
es verdadero esta vez, la aplicación renderizaría el componente Home
.
Por simplicidad, en el ejemplo anterior, el estado de inicio de sesión del usuario se mantiene en una variable global, y ese flag simplemente se activa cuando el usuario hace clic en el botón de inicio de sesión. En un escenario real, un usuario obviamente tendría que proporcionar credenciales de inicio de sesión adecuadas, y hacer clic en el botón de inicio de sesión desencadenaría una solicitud a un servidor para autenticar al usuario:
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,
});
Precarga de datos
Normalmente, un componente puede cargar datos al inicializarse. Cargar datos de esta manera hace que el componente se renderice dos veces: la primera renderización ocurre al enrutar, y la segunda, después de que se complete la solicitud. Ten en cuenta que loadUsers()
devuelve una Promesa, pero cualquier Promesa devuelta por oninit
se ignora actualmente. El segundo pase de renderizado proviene de la opción 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';
},
},
});
En el ejemplo anterior, en la primera renderización, la UI muestra "loading"
ya que state.users
es un array vacío antes de que se complete la solicitud. Luego, una vez que los datos están disponibles, la UI se vuelve a dibujar y se muestra una lista de identificadores de usuario.
Los RouteResolvers
permiten precargar datos antes de la renderización del componente para evitar el parpadeo de la UI y, por lo tanto, evitar la necesidad de un indicador de carga:
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);
});
},
},
});
En este caso, render
solo se ejecuta después de que se completa la solicitud, lo que hace que el operador ternario sea redundante.
División de código (Code splitting)
En una aplicación grande, puede ser deseable descargar el código de cada ruta bajo demanda, en lugar de por adelantado. Esta división del código se denomina 'code splitting' o carga diferida. En Mithril.js, esto se puede lograr devolviendo una promesa desde el hook onmatch
:
En su forma más básica, se podría hacer lo siguiente:
// 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');
},
},
});
Sin embargo, para que esto funcione de forma realista en un entorno de producción, sería necesario empaquetar todas las dependencias del módulo Home.js
en el archivo que finalmente sirve el servidor.
Afortunadamente, existen varias herramientas que facilitan la tarea de agrupar módulos para la carga diferida. Aquí hay un ejemplo usando import(...)
dinámico nativo, compatible con muchos bundlers:
m.route(document.body, '/', {
'/': {
onmatch: function () {
return import('./Home.js');
},
},
});
Rutas tipadas
En algunos casos avanzados de enrutamiento, es posible que desees restringir un valor más allá del propio path, solo coincidiendo con algo como un ID numérico. Puedes hacer eso con bastante facilidad devolviendo m.route.SKIP
desde una ruta.
m.route(document.body, '/', {
'/view/:id': {
onmatch: function (args) {
if (!/^\d+$/.test(args.id)) return m.route.SKIP;
return ItemView;
},
},
'/view/:name': UserView,
});
Rutas ocultas
En contadas ocasiones, es posible que desees ocultar ciertas rutas para algunos usuarios, pero no para todos. Por ejemplo, a un usuario se le podría prohibir ver a un usuario en particular, y en lugar de mostrar un error de permiso, preferirías fingir que no existe y redirigir a una vista 404 en su lugar. En este caso, puedes usar m.route.SKIP
para simular que la ruta no 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,
});
Cancelación / bloqueo de rutas
El onmatch
de RouteResolver
puede evitar que se resuelva una ruta devolviendo una promesa que nunca se completa. Esto se puede usar para detectar intentos redundantes de resolución de rutas y cancelarlos:
m.route(document.body, '/', {
'/': {
onmatch: function (args, requestedPath) {
if (m.route.get() === requestedPath) return new Promise(function () {});
},
},
});
Integración de terceros
En algunos casos, podrías necesitar integrar Mithril con otros frameworks como React. Aquí te mostramos cómo hacerlo:
- Define todas tus rutas usando
m.route
como de costumbre, pero asegúrate de usarlo solo una vez. No se admiten múltiples puntos de ruta. - Cuando necesites eliminar las suscripciones de enrutamiento, usa
m.mount(root, null)
, utilizando el mismo root que usaste enm.route(root, ...)
.m.route
usam.mount
internamente para conectar todo, por lo que no es magia.
Aquí hay un ejemplo con 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} />;
}
}
Y aquí está el equivalente aproximado con 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);
},
});