Skip to content

Enrutamiento

PreffX proporciona un mecanismo ligero de enrutamiento del lado del cliente basado en señales a través de la utilidad url. No hay ningún componente de enrutador que importar — obtienes un ReadonlySignal<URL> reactivo que se mantiene sincronizado con la ubicación del navegador mediante la Navigation API.

La señal url

Cada componente de PreffX recibe url en su segundo argumento (utils).

PropiedadTipoDescripción
urlReadonlySignal<URL>Señal reactiva cuyo .value es el objeto URL actual. Cambia cuando el usuario navega a una página del mismo origen.

Firma de tipo

typescript
type PreffXUtils = {
    url: ReadonlySignal<URL>;
    // ...
};

Enrutamiento básico

Usa url.value para leer el nombre de ruta actual, los parámetros de búsqueda, el hash o cualquier otra propiedad de URL. Combínalo con computed para cambiar reactivamente entre vistas.

tsx
import type { PC } from 'preffx';

const HomePage: PC = () => <div>Página de inicio</div>;
const ContactsPage: PC = () => <div>Contactos</div>;
const NotFound: PC = () => <div>Página no encontrada</div>;

export const App: PC = (_, { computed, url }) => {
    const page = computed(() => {
        switch (url.value.pathname) {
            case '/':
                return <HomePage />;
            case '/contacts':
                return <ContactsPage />;
            default:
                return <NotFound />;
        }
    });

    return (
        <div>
            <nav>
                <a href="/">Inicio</a>
                <a href="/contacts">Contactos</a>
            </nav>
            <main>{page}</main>
        </div>
    );
};

Cómo funciona

  1. Renderizas enlaces <a href="/contacts"> como de costumbre.
  2. Cuando el usuario hace clic en el enlace, el navegador emite un evento navigate.
  3. PreffX intercepta el evento (llama a event.preventDefault()) y actualiza la señal interna url.
  4. Cualquier computed que dependa de url.value se reevalúa y el DOM se actualiza de forma reactiva.

Enlaces de navegación

Usa elementos <a> estándar con href. PreffX maneja la intercepción automáticamente — no se requiere un componente <Link> especial.

tsx
<nav>
    <a href="/">Inicio</a>
    <a href="/about">Acerca de</a>
    <a href="/contacts">Contactos</a>
    <a href="/products?category=books&sort=asc">Libros</a>
</nav>

El interceptor de PreffX solo se activa cuando al menos un componente está usando la señal url (rastreado por un contador interno URL_WATCHERS). Esto significa:

  • Si ningún componente lee url, los clics en enlaces <a> se comportan normalmente (navegación completa de página).
  • Tan pronto como un componente accede a url.value, la navegación es interceptada y manejada del lado del cliente.

Lectura de partes de la URL

Debido a que url es un ReadonlySignal<URL>, puedes leer cualquier propiedad del objeto nativo URL:

tsx
const CurrentRoute: PC = (_, { url }) => {
    return (
        <div>
            <p>Ruta: {url.value.pathname}</p>
            <p>Búsqueda: {url.value.search}</p>
            <p>Hash: {url.value.hash}</p>
            <p>Host: {url.value.host}</p>
            <p>Origen: {url.value.origin}</p>
        </div>
    );
};

Todos estos son reactivos — los cambios en cualquiera de ellos desencadenan el recálculo de las señales dependientes.

Parámetros de ruta (cadenas de consulta)

Analiza los parámetros de consulta desde url.value.searchParams dentro de un computed:

tsx
import type { PC } from 'preffx';

const ProductList: PC = (_, { computed, url }) => {
    const category = computed(() =>
        url.value.searchParams.get('category') ?? 'todos'
    );
    const sort = computed(() =>
        url.value.searchParams.get('sort') ?? 'nombre'
    );

    return (
        <div>
            <p>Categoría: {category}</p>
            <p>Ordenar por: {sort}</p>
            <a href="/products?category=books&sort=asc">Libros (asc)</a>
            <a href="/products?category=books&sort=desc">Libros (desc)</a>
        </div>
    );
};

Segmentos dinámicos (parámetros de ruta)

Dado que url te proporciona el objeto URL completo, puedes analizar los segmentos de ruta manualmente:

tsx
import type { PC } from 'preffx';

const UserProfile: PC = (_, { computed, url }) => {
    const segments = computed(() => url.value.pathname.split('/').filter(Boolean));
    // pathname = "/users/42"  →  segments = ["users", "42"]

    const userId = computed(() => segments.value[1]);

    return <div>ID de usuario: {userId}</div>;
};

Para un enfoque más estructurado, construye un simple coincidente de rutas:

tsx
import type { PC } from 'preffx';

// Patrón de ruta simple → extractor de parámetros
function match(pattern: string, pathname: string): Record<string, string> | null {
    const patternParts = pattern.split('/').filter(Boolean);
    const pathParts = pathname.split('/').filter(Boolean);
    if (patternParts.length !== pathParts.length) return null;

    const params: Record<string, string> = {};
    for (let i = 0; i < patternParts.length; i++) {
        if (patternParts[i].startsWith(':')) {
            params[patternParts[i].slice(1)] = pathParts[i];
        } else if (patternParts[i] !== pathParts[i]) {
            return null;
        }
    }
    return params;
}

const RouteExample: PC = (_, { computed, url }) => {
    const route = computed(() => {
        const { pathname } = url.value;
        const params = match('/users/:id', pathname);
        if (params) return <div>Usuario {params.id}</div>;

        const postParams = match('/posts/:postId', pathname);
        if (postParams) return <div>Publicación {postParams.postId}</div>;

        return <div>No encontrado</div>;
    });

    return (
        <div>
            <nav>
                <a href="/users/42">Usuario 42</a>
                <a href="/users/7">Usuario 7</a>
                <a href="/posts/abc">Publicación abc</a>
            </nav>
            <main>{route}</main>
        </div>
    );
};

El interceptor de navegación solo se aplica a navegaciones del mismo origen. Los enlaces entre orígenes (diferente protocolo, host o puerto) no se modifican y provocan una carga completa de la página como es normal.

tsx
<a href="https://example.com">Enlace externo</a>  <!-- navegación completa -->
<a href="/internal">Enlace interno</a>             <!-- navegación del lado del cliente -->

Soporte del navegador

El enrutamiento depende de la Navigation API (globalThis.navigation).

Desde enero de 2026, esta función funciona en los dispositivos y versiones de navegador más recientes. Es posible que esta función no funcione en dispositivos o navegadores antiguos.

Publicado bajo la Licencia Apache 2.0