Navegación Dinámica en Vue Router: Rutas Anidadas y Parámetros Avanzados
Este tutorial te guiará a través de la implementación de rutas anidadas y el manejo de parámetros de ruta avanzados en Vue Router. Aprenderás a construir interfaces de usuario complejas y dinámicas, mejorando la organización de tu código y la experiencia del usuario. Cubriremos desde la configuración básica hasta casos de uso avanzados.
Introducción a la Navegación Dinámica con Vue Router ✨
Vue Router es el compañero oficial de Vue.js para la gestión de rutas. Permite mapear componentes de Vue a URLs específicas, creando aplicaciones de una sola página (SPA) con navegación fluida y sin recargas de página. En este tutorial, nos sumergiremos en dos características potentes: las rutas anidadas y los parámetros de ruta avanzados, que son fundamentales para construir aplicaciones Vue 3 robustas y escalables.
Las rutas anidadas nos permiten organizar la interfaz de usuario de forma jerárquica, mostrando diferentes sub-componentes dentro de un componente padre, basándose en la URL. Los parámetros de ruta, por otro lado, hacen que nuestras rutas sean dinámicas, permitiéndonos pasar información específica a los componentes, como el ID de un producto o el nombre de un usuario.
¿Por qué son importantes las Rutas Anidadas y los Parámetros? 🤔
Imagina una aplicación de comercio electrónico. Tendrás una página para 'Productos', y dentro de esa página, querrás ver 'Detalles del Producto' para un producto específico. Además, dentro de los detalles, podrías tener pestañas para 'Descripción', 'Reseñas' o 'Especificaciones'. Aquí es donde las rutas anidadas brillan. Los parámetros de ruta te permitirían especificar qué producto mostrar, por ejemplo, /productos/123 donde 123 es el ID del producto.
🛠️ Configuración Inicial de Vue Router
Antes de sumergirnos en las rutas anidadas y los parámetros, necesitamos tener una configuración básica de Vue Router. Si ya tienes un proyecto Vue 3 con Vue Router instalado, puedes saltarte esta sección.
Instalación de Vue Router
Si aún no tienes Vue Router instalado en tu proyecto Vue 3, puedes hacerlo con npm o yarn:
npm install vue-router@4
# o
yarn add vue-router@4
Creación del Router Básico
Primero, crearemos un archivo router/index.js (o similar) para configurar nuestro enrutador:
// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router';
import HomeView from '../views/HomeView.vue';
import AboutView from '../views/AboutView.vue';
const routes = [
{
path: '/',
name: 'home',
component: HomeView,
},
{
path: '/about',
name: 'about',
component: AboutView,
},
];
const router = createRouter({
history: createWebHistory(),
routes,
});
export default router;
Luego, lo integramos en nuestro archivo main.js:
// src/main.js
import { createApp } from 'vue';
import App from './App.vue';
import router from './router'; // Importa tu configuración de router
createApp(App).use(router).mount('#app');
Finalmente, en tu App.vue, usarás <router-view> para renderizar el componente correspondiente a la ruta actual y <router-link> para la navegación:
<!-- src/App.vue -->
<template>
<nav>
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</nav>
<router-view />
</template>
Ahora tienes una configuración básica para empezar. ¡Manos a la obra!
🌳 Rutas Anidadas: Organizando tu UI
Las rutas anidadas (o nested routes) permiten que los componentes hijos se rendericen dentro del <router-view> de un componente padre. Esto es ideal para layouts complejos donde diferentes partes de la interfaz de usuario cambian independientemente o se superponen.
Creando un Componente Padre con Rutas Anidadas
Vamos a crear un ejemplo con una sección de Usuarios. Queremos tener una vista principal de Usuarios y, dentro de ella, vistas para Lista de Usuarios y Detalle del Usuario.
Primero, crearemos un componente UserLayout.vue que actuará como el contenedor para nuestras rutas anidadas:
<!-- src/views/UserLayout.vue -->
<template>
<div class="user-layout">
<h2>Sección de Usuarios</h2>
<nav>
<router-link to="/users">Lista de Usuarios</router-link> |
<router-link to="/users/123">Usuario #123</router-link>
<!-- Otros enlaces para sub-rutas -->
</nav>
<div class="user-content">
<router-view /> <!-- Aquí se renderizarán los componentes hijos -->
</div>
</div>
</template>
<script>
export default {
name: 'UserLayout',
};
</script>
<style scoped>
.user-layout {
padding: 20px;
border: 1px solid #ccc;
margin-top: 20px;
}
.user-content {
margin-top: 15px;
padding: 15px;
background-color: #f9f9f9;
border: 1px dashed #eee;
}
</style>
Definiendo las Rutas Anidadas
Ahora, modificaremos nuestro router/index.js para incluir las rutas anidadas. Necesitamos componentes para la lista de usuarios y para el detalle de un usuario. Creemos UserList.vue y UserDetail.vue:
<!-- src/views/UserList.vue -->
<template>
<div>
<h3>Listado de Usuarios</h3>
<ul>
<li><router-link to="/users/john-doe">John Doe</router-link></li>
<li><router-link to="/users/jane-smith">Jane Smith</router-link></li>
<li><router-link to="/users/123">Usuario con ID 123</router-link></li>
</ul>
<p>Selecciona un usuario para ver sus detalles.</p>
</div>
</template>
<script>
export default {
name: 'UserList',
};
</script>
<!-- src/views/UserDetail.vue -->
<template>
<div>
<h3>Detalles del Usuario: {{ $route.params.id }}</h3>
<p>Aquí se mostrará la información detallada del usuario.</p>
<p>ID de usuario actual: <strong>{{ userId }}</strong></p>
<details open><summary>Más Opciones para Usuario</summary>
<ul>
<li><router-link :to="{ name: 'user-profile', params: { id: userId } }">Ver Perfil</router-link></li>
<li><router-link :to="{ name: 'user-settings', params: { id: userId } }">Ajustes</router-link></li>
</ul>
<router-view /> <!-- Para sub-rutas anidadas dentro del detalle -->
</details>
</div>
</template>
<script>
import { useRoute } from 'vue-router';
import { computed } from 'vue';
export default {
name: 'UserDetail',
setup() {
const route = useRoute();
const userId = computed(() => route.params.id);
return { userId };
},
};
</script>
Ahora, actualicemos router/index.js:
// src/router/index.js (fragmento relevante)
import UserLayout from '../views/UserLayout.vue';
import UserList from '../views/UserList.vue';
import UserDetail from '../views/UserDetail.vue';
const routes = [
// ... otras rutas
{
path: '/users',
component: UserLayout, // Componente padre que contiene un <router-view>
children: [ // Definimos las rutas anidadas aquí
{
path: '', // Ruta por defecto para /users (UserList se renderizará en el <router-view> de UserLayout)
name: 'user-list',
component: UserList,
},
{
path: ':id',
name: 'user-detail',
component: UserDetail,
props: true, // Pasa los parámetros de ruta como props al componente
children: [
// Más anidación: Rutas dentro de UserDetail
{
path: 'profile',
name: 'user-profile',
component: { template: '<div class="callout note">📌 <strong>Perfil de Usuario:</strong> Mostrando información detallada de {{ $route.params.id }}</div>' },
},
{
path: 'settings',
name: 'user-settings',
component: { template: '<div class="callout tip">💡 <strong>Ajustes de Usuario:</strong> Configuración para {{ $route.params.id }}</div>' },
},
],
},
],
},
];
Con esta configuración:
- Cuando navegas a
/users, se renderizaUserLayouty dentro de su<router-view>,UserList. - Cuando navegas a
/users/john-doeo/users/123, se renderizaUserLayout, y dentro de su<router-view>,UserDetail. Además, el parámetroid(john-doeo123) estará disponible enUserDetail. - Cuando navegas a
/users/123/profile,UserLayoutrenderizaUserDetail, yUserDetaila su vez renderiza el componente deprofiledentro de su propio<router-view>.
🧩 Parámetros de Ruta Avanzados
Los parámetros de ruta nos permiten crear rutas dinámicas donde una parte de la URL puede variar. Ya vimos un ejemplo básico con :id. Ahora profundicemos en opciones más avanzadas.
1. Parámetros Obligatorios (:paramName)
Son los más comunes. Si el parámetro no está presente en la URL, la ruta no coincidirá.
Ejemplo: /users/:id donde :id es el parámetro. Puedes acceder a él a través de $route.params.id en el componente o mediante useRoute() en el setup script.
// En UserDetail.vue (o cualquier componente de ruta)
import { useRoute } from 'vue-router';
import { computed } from 'vue';
export default {
name: 'UserDetail',
props: ['id'], // Si usas props: true en la configuración de la ruta
setup(props) { // props contendrá el 'id' si props: true
const route = useRoute();
const userIdFromRoute = computed(() => route.params.id);
// Si usas props: true, puedes usar props.id directamente
const userIdFromProps = computed(() => props.id);
return {
userIdFromRoute,
userIdFromProps
};
},
};
2. Parámetros Opcionales (:paramName?)
¿Qué pasa si un parámetro puede estar presente o no? Usa el signo de interrogación ?.
Ejemplo: '/products/:category?'
// src/router/index.js (fragmento)
{
path: '/products/:category?', // 'category' es opcional
name: 'products',
component: () => import('../views/ProductsView.vue'),
},
<!-- src/views/ProductsView.vue -->
<template>
<div>
<h2>Página de Productos</h2>
<p v-if="$route.params.category">
Mostrando productos en la categoría: <strong>{{ $route.params.category }}</strong>
</p>
<p v-else>
Mostrando todos los productos.
</p>
<nav>
<router-link to="/products">Todos</router-link> |
<router-link to="/products/electronics">Electrónica</router-link> |
<router-link to="/products/books">Libros</router-link>
</nav>
</div>
</template>
<script>
export default {
name: 'ProductsView',
};
</script>
Ahora, /products mostrará todos los productos, y /products/electronics mostrará los de esa categoría.
3. Parámetros Repetidos/Múltiples (:paramName+ o :paramName*)
A veces, necesitas que un parámetro coincida con uno o más segmentos de URL, o cero o más. Aquí entran los repetidos (+) y los cero o más (*).
+: Coincide con uno o más segmentos.'/files/:paths+'coincidirá con/files/a,/files/a/b, pero no con/files.*: Coincide con cero o más segmentos.'/files/:paths*'coincidirá con/files,/files/a,/files/a/b.
Los valores se obtendrán como un array.
Ejemplo: Para una ruta de descarga de archivos con una estructura de carpetas variable.
// src/router/index.js (fragmento)
{
path: '/download/:folder+',
name: 'download-files',
component: () => import('../views/DownloadView.vue'),
props: true,
},
<!-- src/views/DownloadView.vue -->
<template>
<div>
<h2>Descarga de Archivos</h2>
<p>Ruta de la carpeta: <code>/{{ folder.join('/') }}</code></p>
<p>Contenido de la carpeta:</p>
<ul>
<li v-for="(item, index) in folder" :key="index">{{ item }}</li>
</ul>
<p class="callout tip">💡 <strong>Navegar:</strong>
<router-link :to="{ name: 'download-files', params: { folder: ['docs', 'reports', 'reporte-final.pdf'] } }">
/download/docs/reports/reporte-final.pdf
</router-link>
</p>
</div>
</template>
<script>
import { useRoute } from 'vue-router';
import { computed } from 'vue';
export default {
name: 'DownloadView',
props: ['folder'], // 'folder' será un array debido a :folder+
setup(props) {
// Si no se usa props: true, se accedería vía route.params.folder
const route = useRoute();
const folderFromRoute = computed(() => route.params.folder);
return { folder: props.folder || folderFromRoute }; // Usar props si está disponible
},
};
</script>
- Ir a
/download/images/logo.pngresultará enfolder: ['images', 'logo.png']. - Ir a
/download/documents/invoices/2023/q4/invoice-123.pdfresultará enfolder: ['documents', 'invoices', '2023', 'q4', 'invoice-123.pdf'].
4. Parámetros de Expresión Regular (:paramName(regex))
Para un control más fino sobre los parámetros, puedes definir expresiones regulares. Esto es útil para validar el formato de un parámetro directamente en la ruta.
Ejemplo: Un ID de producto que siempre es numérico.
// src/router/index.js (fragmento)
{
path: '/products/:id(\d+)', // Solo números para 'id'
name: 'product-numeric-id',
component: () => import('../views/ProductDetailRegex.vue'),
props: true,
},
{
path: '/articles/:slug([a-z0-9-]+)', // Slug con letras minúsculas, números y guiones
name: 'article-slug',
component: () => import('../views/ArticleDetailRegex.vue'),
props: true,
},
<!-- src/views/ProductDetailRegex.vue -->
<template>
<div>
<h3>Detalle de Producto (ID Numérico)</h3>
<p>ID del producto: <mark>{{ id }}</mark></p>
<div class="callout warning">⚠️ <strong>Advertencia:</strong> Esta ruta solo coincide si el ID es un número.</div>
</div>
</template>
<script>
export default {
name: 'ProductDetailRegex',
props: ['id'],
};
</script>
<!-- src/views/ArticleDetailRegex.vue -->
<template>
<div>
<h3>Detalle del Artículo (Slug)</h3>
<p>Slug del artículo: <mark>{{ slug }}</mark></p>
<div class="callout note">📌 <strong>Nota:</strong> Esta ruta espera un slug con letras minúsculas, números y guiones.</div>
</div>
</template>
<script>
export default {
name: 'ArticleDetailRegex',
props: ['slug'],
};
</script>
/products/123coincidirá./products/abcno coincidirá con esta ruta (pero podría coincidir con otra si existe, o llevar a un 404).
5. Parámetros de Petición (Query Parameters) y Hash
Aunque no son parte de la path de la ruta, los query parameters (?key=value&key2=value2) y el hash (#section) son formas comunes de pasar datos adicionales y también son accesibles a través del objeto $route.
this.$route.query: Objeto con los parámetros de petición.this.$route.hash: Cadena del hash (incluyendo el#).
Ejemplo: /search?q=vuejs&page=1#results
<!-- src/views/SearchResults.vue -->
<template>
<div>
<h2>Resultados de Búsqueda</h2>
<p>Término de búsqueda: <strong>{{ $route.query.q }}</strong></p>
<p>Página actual: <strong>{{ $route.query.page || 1 }}</strong></p>
<p v-if="$route.hash">Sección de Hash: <strong>{{ $route.hash }}</strong></p>
<nav>
<router-link :to="{ path: '/search', query: { q: 'vue-router', page: 2 }, hash: '#top' }">
Buscar Vue Router (Pág. 2)
</router-link>
</nav>
</div>
</template>
<script>
export default {
name: 'SearchResults',
};
</script>
🔄 Navegación Programática y Acceso a la Información de Ruta
Vue Router no solo permite la navegación declarativa con <router-link>, sino también la navegación programática usando router.push(), router.replace(), router.go(), etc. Esto es útil para redirigir después de una acción de usuario o una llamada a una API.
Navegación Programática con Parámetros
import { useRouter } from 'vue-router';
export default {
// ...
setup() {
const router = useRouter();
const goToUserDetail = (id) => {
router.push({ name: 'user-detail', params: { id: id } });
};
const searchProducts = (term, page) => {
router.push({ path: '/search', query: { q: term, page: page } });
};
const goToDownloadPath = (pathSegments) => {
// pathSegments debe ser un array para :param+
router.push({ name: 'download-files', params: { folder: pathSegments } });
};
return { goToUserDetail, searchProducts, goToDownloadPath };
},
};
Acceso a la Información de Ruta en Componentes
Como ya hemos visto, puedes acceder al objeto route de varias maneras:
- Dentro del
setup(Vue 3 Composition API):
import { useRoute } from 'vue-router';
const route = useRoute();
console.log(route.params.id);
console.log(route.query.q);
- Dentro del
optionsAPI (this.$route):
export default {
mounted() {
console.log(this.$route.params.id);
console.log(this.$route.query.q);
},
};
- Mediante
props: true: Si la ruta tieneprops: true, los parámetros se pasarán directamente como props al componente, simplificando su uso y mejorando la reusabilidad.
export default {
props: ['id', 'category', 'folder'],
// ... puedes usar this.id, this.category, etc.
};
// Ejemplo de watch en un componente
import { useRoute } from 'vue-router';
import { watch } from 'vue';
export default {
setup() {
const route = useRoute();
watch(
() => route.params.id, // Observa solo el parámetro 'id'
(newId, oldId) => {
console.log(`El ID de usuario cambió de ${oldId} a ${newId}`);
// Aquí puedes cargar nuevos datos basados en el newId
}
);
watch(
() => route.query.q, // Observa el parámetro de query 'q'
(newQuery, oldQuery) => {
console.log(`El término de búsqueda cambió de ${oldQuery} a ${newQuery}`);
// Realizar nueva búsqueda
}
);
return {};
},
};
Casos de Uso Avanzados y Mejores Prácticas 🎯
Nombres de Ruta y Mapeo de Parámetros
Siempre es una buena práctica usar nombres de ruta (name: 'my-route') en lugar de rutas absolutas (path: '/my-route') cuando navegas programáticamente o con <router-link>. Esto hace que tu código sea más robusto a cambios en la estructura de URLs.
Tabla: Pros y Contras de Nombres vs Rutas Absolutas
| Característica | Uso de name (Nombres de Ruta) | Uso de path (Rutas Absolutas) |
|---|---|---|
| Mantenimiento | Alto. Si la URL cambia, solo actualizas la definición de ruta. | Bajo. Cada vez que la URL cambia, debes actualizar todos los enlaces. |
| Claridad | Alta. El nombre describe el propósito de la ruta. | Media. La URL puede ser menos descriptiva del propósito. |
| Parámetros | Fácil de pasar como objeto params. | Requiere concatenar strings para construir la URL. |
| Query Params/Hash | Fácil de añadir con propiedades query y hash. | Requiere concatenar manualmente a la string del path. |
| Flexibilidad (anidadas) | Óptimo. Permite navegar a sub-rutas anidadas con facilidad. | Menos flexible, más propenso a errores. |
Manejo de Rutas no Encontradas (404) 🌐
Es fundamental tener una ruta catch-all para manejar URLs que no coinciden con ninguna de tus rutas definidas. Se define con un parámetro catch-all que utiliza (.*). Debe ser la última ruta en tu configuración.
// src/router/index.js (al final de la array 'routes')
{
path: '/:pathMatch(.*)*', // Coincide con cualquier cosa no capturada antes
name: 'NotFound',
component: () => import('../views/NotFound.vue'),
},
<!-- src/views/NotFound.vue -->
<template>
<div class="not-found">
<h1>404 - Página no encontrada</h1>
<p>Lo sentimos, la página que buscas no existe.</p>
<router-link to="/">Ir a la página de inicio</router-link>
</div>
</template>
<script>
export default {
name: 'NotFound',
};
</script>
<style scoped>
.not-found {
text-align: center;
padding: 50px;
background-color: #f8f8f8;
border: 1px solid #eee;
margin: 30px;
}
h1 {
color: #d9534f;
}
</style>
Alias de Ruta
Puedes darle alias a una ruta para que la misma URL se comporte como múltiples rutas, o para tener URLs alternativas que mapeen a la misma vista.
{
path: '/home',
alias: '/', // '/home' y '/' mostrarán el mismo componente
name: 'home',
component: HomeView,
},
{
path: '/users/:id',
name: 'user-detail',
component: UserDetail,
props: true,
alias: '/profile/:id', // '/users/123' y '/profile/123' apuntan a UserDetail
},
Redirecciones
Vue Router permite redirecciones de manera sencilla:
{
path: '/old-path',
redirect: '/new-path',
},
{
path: '/legacy-users/:id',
redirect: { name: 'user-detail' }, // Redirecciona por nombre de ruta
},
// Redirección dinámica
{
path: '/products/:id',
redirect: to => {
// Puedes hacer lógica aquí basada en 'to' (objeto de ruta actual)
return { path: `/new-products/${to.params.id}/overview` };
},
},
Transiciones de Ruta
Para una experiencia de usuario más pulida, puedes añadir transiciones a tus <router-view>.
<template>
<router-view v-slot="{ Component }">
<transition name="fade" mode="out-in">
<component :is="Component" />
</transition>
</router-view>
</template>
<style>
.fade-enter-active, .fade-leave-active {
transition: opacity 0.5s ease;
}
.fade-enter-from, .fade-leave-to {
opacity: 0;
}
</style>
Conclusión 🎉
Has llegado al final de este completo tutorial sobre navegación dinámica en Vue Router. Hemos explorado a fondo las rutas anidadas para construir interfaces de usuario jerárquicas y los parámetros de ruta avanzados (obligatorios, opcionales, repetidos y con expresiones regulares) para crear URLs dinámicas y flexibles. También cubrimos cómo acceder a la información de la ruta, la navegación programática y mejores prácticas como el manejo de 404s y las transiciones.
Dominar estas características de Vue Router te permitirá diseñar aplicaciones Vue 3 complejas con una estructura de navegación sólida y una experiencia de usuario optimizada. Sigue practicando y experimentando con diferentes escenarios para solidificar tus conocimientos.
¡Feliz codificación! 🚀
Tutoriales relacionados
Comentarios (0)
Aún no hay comentarios. ¡Sé el primero!