Creando un componente <Route> declarativo
En esta clase aprenderás a transformar un router con lógica imperativa (basado en if y switch) en un sistema declarativo usando un componente <Route>. Este cambio mejorará drásticamente la legibilidad, escalabilidad y mantenibilidad de tu aplicación.
El problema: Lógica imperativa en el componente principal
Actualmente, nuestro router funciona con condicionales en el componente principal:
// App.jsx - Versión con if
import { useRouter } from './hooks/useRouter'
import { Home } from './pages/Home'
import { Search } from './pages/Search'
import { About } from './pages/About'
function App() {
const { currentPath } = useRouter()
// ❌ Problema: Lógica imperativa con if
if (currentPath === '/') {
return <Home />
}
if (currentPath === '/search') {
return <Search />
}
if (currentPath === '/about') {
return <About />
}
return <NotFound />
}
export default App
Alternativa con switch (tampoco es ideal)
Alguien podría sugerir usar switch para mejorar el código:
// App.jsx - Versión con switch
function App() {
const { currentPath } = useRouter()
// ❌ Sigue siendo imperativo
switch (currentPath) {
case '/':
return <Home />
case '/search':
return <Search />
case '/about':
return <About />
default:
return <NotFound />
}
}
¿Por qué no es ideal?
Aunque switch está mejor que múltiples if, ambos enfoques tienen problemas:
❌ Lógica imperativa: Describes cómo funciona el routing, no qué debe hacer ❌ No escala bien: Añadir rutas significa modificar la lógica del componente ❌ Difícil de leer: No es evidente qué rutas existen de un vistazo ❌ No es declarativo: No expresa la intención claramente ❌ Lógica acoplada: El componente principal conoce los detalles del routing
La solución: Componente <Route> declarativo
En lugar de condicionales, vamos a crear un componente <Route> que encapsule la lógica de matching.
Ventajas del enfoque declarativo
✅ Expresa intención: Cada ruta es clara y autoexplicativa
✅ Fácil de leer: Ves todas las rutas de un vistazo
✅ Escalable: Añadir rutas es solo añadir más <Route>
✅ Separación de responsabilidades: Cada <Route> gestiona su propia lógica
✅ Sin condicionales: El componente principal queda limpio
Creando el componente <Route>
Paso 1: Crear el archivo del componente
Vamos a crear un nuevo archivo para nuestro componente:
// components/Route.jsx
import { useRouter } from '../hooks/useRouter'
export function Route({ path, component: Component }) {
// Obtener la ruta actual del router
const { currentPath } = useRouter()
// Si la ruta no coincide, no renderizar nada
if (currentPath !== path) {
return null
}
// Si coincide, renderizar el componente
return <Component />
}
Desglosando el código
1. Reutilizamos el custom hook useRouter()
const { currentPath } = useRouter()
Aquí es donde se ve claramente el poder del custom hook que creamos en la clase anterior:
- ✅ Centraliza la lógica del router
- ✅ Se puede reutilizar en diferentes componentes
- ✅ Mantiene el código limpio y organizado
2. Decisión de renderizado
if (currentPath !== path) {
return null
}
Este es el patrón de renderizado condicional en React:
- Si la ruta actual NO coincide con la ruta del componente → no renderiza nada (
null) - Si coinciden → renderiza el componente
3. Prop component con destructuring
export function Route({ path, component: Component }) {
return <Component />
}
Usamos destructuring con renaming:
component: Component→ Recibimoscomponentpero lo renombramos aComponent(con mayúscula)- ¿Por qué? En JSX, los componentes deben empezar con mayúscula
- Esto nos permite hacer
<Component />en lugar de<component />
Usando el componente <Route> en App
Antes (imperativo)
// App.jsx - ANTES
import { useRouter } from './hooks/useRouter'
import { Home } from './pages/Home'
import { Search } from './pages/Search'
import { About } from './pages/About'
function App() {
const { currentPath } = useRouter()
if (currentPath === '/') return <Home />
if (currentPath === '/search') return <Search />
if (currentPath === '/about') return <About />
return <NotFound />
}
Después (declarativo)
// App.jsx - DESPUÉS
import { Route } from './components/Route'
import { Home } from './pages/Home'
import { Search } from './pages/Search'
import { About } from './pages/About'
import { NotFound } from './pages/NotFound'
function App() {
return (
<>
<Route path="/" component={Home} />
<Route path="/search" component={Search} />
<Route path="/about" component={About} />
<Route path="/404" component={NotFound} />
</>
)
}
export default App
¿Qué cambió?
✅ Sin condicionales: Nada de if, switch o ternarios
✅ Declarativo: Cada línea declara una ruta claramente
✅ Auto-gestionado: Cada <Route> decide si debe renderizarse
✅ Escalable: Añadir rutas es solo añadir más <Route>
✅ Legible: Ves todas las rutas de un vistazo
Cómo funciona internamente
Vamos a entender qué pasa cuando renderizamos estas rutas:
<>
<Route path="/" component={Home} />
<Route path="/search" component={Search} />
<Route path="/about" component={About} />
</>
Si estamos en /
-
Primera ruta (
path="/")currentPath === "/"→ ✅ Coincide- Renderiza
<Home />
-
Segunda ruta (
path="/search")currentPath !== "/search"→ ❌ No coincide- Retorna
null(no renderiza nada)
-
Tercera ruta (
path="/about")currentPath !== "/about"→ ❌ No coincide- Retorna
null(no renderiza nada)
Resultado final: Solo se renderiza <Home />
Si estamos en /search
-
Primera ruta (
path="/")currentPath !== "/"→ ❌ No coincide- Retorna
null
-
Segunda ruta (
path="/search")currentPath === "/search"→ ✅ Coincide- Renderiza
<Search />
-
Tercera ruta (
path="/about")currentPath !== "/about"→ ❌ No coincide- Retorna
null
Resultado final: Solo se renderiza <Search />
Patrón de renderizado con null
Renderizar null es un patrón muy común en React:
function ConditionalComponent({ shouldShow, children }) {
// Si no debe mostrarse, renderiza null
if (!shouldShow) {
return null
}
// Si debe mostrarse, renderiza el contenido
return <div>{children}</div>
}
Ventajas:
✅ Limpio: No ensucia el DOM con elementos vacíos
✅ Performante: React optimiza el renderizado de null
✅ Idiomático: Es el estándar en React para “no renderizar nada”
Comparación: Imperativo vs Declarativo
Imperativo (con if)
function App() {
const { currentPath } = useRouter()
// Describes CÓMO decidir qué renderizar
if (currentPath === '/') {
return <Home />
} else if (currentPath === '/search') {
return <Search />
} else if (currentPath === '/about') {
return <About />
} else {
return <NotFound />
}
}
❌ Desventajas:
- Lógica imperativa (“si esto, entonces aquello”)
- El componente principal conoce todos los detalles
- No es fácilmente extensible
- Difícil de testear cada ruta independientemente
Declarativo (con <Route>)
function App() {
// Declaras QUÉ debe pasar en cada ruta
return (
<>
<Route path="/" component={Home} />
<Route path="/search" component={Search} />
<Route path="/about" component={About} />
<Route path="/404" component={NotFound} />
</>
)
}
✅ Ventajas:
- Declarativo (“en esta ruta, este componente”)
- Cada ruta es autónoma
- Fácil añadir/quitar rutas
- Fácil de testear cada
<Route>independientemente
Añadiendo un layout compartido
Ahora que tenemos un sistema declarativo, es muy fácil añadir elementos compartidos:
function App() {
return (
<>
<Header />
<main>
<Route path="/" component={Home} />
<Route path="/search" component={Search} />
<Route path="/about" component={About} />
<Route path="/contact" component={Contact} />
</main>
<Footer />
</>
)
}
Lo que pasa:
- El
<Header />siempre se renderiza - Solo una de las rutas se renderiza (la que coincide)
- El
<Footer />siempre se renderiza
Próximos pasos
Con este patrón declarativo, hemos sentado las bases para un router profesional. En las próximas clases aprenderemos:
- Rutas dinámicas:
/users/:id - Rutas anidadas: Rutas dentro de rutas
- Protección de rutas: Rutas privadas que requieren autenticación
- Navegación programática: Navegar desde eventos
- React Router: Migrar a la librería oficial
Conceptos clave aprendidos
| Concepto | Descripción |
|---|---|
| Programación declarativa | Expresar qué queremos, no cómo lograrlo |
| Programación imperativa | Describir paso a paso cómo hacer algo |
| Renderizado condicional | Componentes que deciden si renderizar o no |
Retornar null | Patrón para indicar “no renderizar nada” |
| Composición de componentes | Crear componentes que usan otros componentes |
| Props con renaming | component: Component para usar mayúsculas |
| Reutilización de hooks | useRouter() usado en múltiples lugares |
Ventajas del patrón <Route>
| Ventaja | Descripción |
|---|---|
| 🎯 Declarativo | Expresa claramente la intención |
| 📖 Legible | Fácil de entender de un vistazo |
| 🔧 Mantenible | Cambios localizados en cada ruta |
| 📈 Escalable | Añadir rutas no complica el código |
| 🧩 Modular | Cada ruta es independiente |
| ✅ Testeable | Fácil testear cada ruta por separado |
| 🔄 Reutilizable | El componente <Route> es reusable |
Conclusión
En esta clase has aprendido a:
- ✅ Identificar problemas de código imperativo con
ifyswitch - ✅ Crear un componente
<Route>declarativo y reutilizable - ✅ Reutilizar el custom hook
useRouter()en diferentes componentes - ✅ Usar el patrón de renderizado condicional con
null - ✅ Aplicar programación declarativa en React
- ✅ Entender cómo funciona internamente el routing declarativo
- ✅ Crear una base sólida para un sistema de routing escalable
Este patrón declarativo es la base de cómo funcionan los routers profesionales como React Router. Has construido una versión simplificada pero completamente funcional que te ayudará a entender los conceptos fundamentales del routing en aplicaciones web modernas.
💡 Recuerda: En producción siempre debes usar React Router o una librería similar, pero entender cómo funciona internamente te hace un mejor desarrollador.