Componentes Integrados
PreffX incluye cuatro componentes integrados accesibles a través del segundo argumento (utils) de cada componente. Resuelven patrones comunes de UI sin dependencias adicionales.
| Componente | Propósito | Disponibilidad |
|---|---|---|
For | Renderizar listas con elementos reactivos | utils.For |
Catch | Capturar y manejar errores de renderizado | utils.Catch |
Portal | Renderizar hijos en un nodo DOM diferente | utils.Portal |
Defer | Mostrar un marcador de posición hasta que un valor esté listo | utils.Defer |
For
Renderizador de listas reactivo. Acepta una señal de elementos (o un array simple) y solo vuelve a renderizar los elementos que cambian. Soporta un fallback opcional que se muestra cuando la lista está vacía.
Firma de tipo
const For: PC<{
items: Signal<any[]>;
callback: (item: any) => any;
fallback?: any;
}>Propiedades
| Propiedad | Tipo | Requerido | Descripción |
|---|---|---|---|
items | Signal<any[]> | Sí | Una señal que contiene el array de elementos. Cuando la señal cambia, For reconcilia eficientemente la lista — los elementos añadidos se renderizan, los elementos eliminados se limpian. |
callback | (item: any) => any | Sí | Función llamada para cada elemento. Devuelve el JSX a renderizar para ese elemento. El valor devuelto se almacena en caché y solo se recalcula cuando el valor del elemento en sí cambia. |
fallback | any | No | Contenido renderizado cuando el array está vacío. |
Uso
import type { PC } from 'preffx';
type Item = { id: number; name: string };
const App: PC = (_, { signal, For }) => {
const items = signal<Item[]>([
{ id: 1, name: 'Alpha' },
{ id: 2, name: 'Beta' },
{ id: 3, name: 'Gamma' },
]);
return (
<ul>
<For
items={items}
callback={(item: Item) => (
<li key={item.id}>{item.name}</li>
)}
fallback={<li>Sin elementos</li>}
/>
</ul>
);
};Actualizaciones reactivas
Cuando mutas el array en la señal, el DOM se actualiza automáticamente:
// Añadir un elemento
items.value = [...items.value, { id: 4, name: 'Delta' }];
// Eliminar un elemento
items.value = items.value.filter((i) => i.id !== 2);
// Reemplazar todos los elementos
items.value = [];
// -> se muestra el fallback "Sin elementos"Elementos señal dentro del array
Cada elemento del array puede ser a su vez una señal. Cuando el valor de la señal de un elemento cambia, solo se recalcula la salida de ese elemento:
const items = signal([
signal({ id: 1, name: 'Alpha' }),
signal({ id: 2, name: 'Beta' }),
]);
// Actualizar el segundo elemento — solo se vuelve a renderizar "Beta"
items.value[1].value = { id: 2, name: 'BETA' };Catch
Captura errores lanzados (o devueltos) por componentes hijos y renderiza un contenido alternativo en su lugar. Inspirado en los Límites de Error de React.
Firma de tipo
const Catch: PC<{
fallback?: any;
children?: any | any[];
}>Propiedades
| Propiedad | Tipo | Requerido | Descripción |
|---|---|---|---|
children | any | any[] | No | Hijos a renderizar. Si algún hijo es (o devuelve) una instancia de Error, el error es capturado. |
fallback | any | No | Contenido a mostrar cuando se capturan errores. Puede ser un valor estático o un componente función que recibe { errors, ...extraProps }. |
Fallback como contenido estático
import type { PC } from 'preffx';
const App: PC = (_, { Catch }) => {
return (
<div>
<Catch fallback={<div>Algo salió mal</div>}>
<MaybeBrokenComponent />
</Catch>
</div>
);
};Fallback como componente función
La función fallback recibe errors (el array de errores capturados) más cualquier propiedad adicional pasada a <Catch>:
const App: PC = (_, { Catch }) => {
return (
<Catch
fallback={({ errors }) => (
<div class="error-fallback">
<h3>Ocurrió(eron) {errors.length} error(es)</h3>
</div>
)}
>
<UnstableComponent />
</Catch>
);
};Pasando propiedades extra al fallback
const App: PC = (_, { Catch }) => {
return (
<Catch
fallback={({ errors, severity }) => (
<div class={severity}>
Errores: {errors.length}
</div>
)}
severity="critical"
>
<UnstableComponent />
</Catch>
);
};Cómo se detectan los errores
Catch escanea todos los hijos recursivamente (usando flat(Infinity)). Cualquier hijo que sea una instancia de Error (mediante instanceof Error) activa el contenido alternativo.
Portal
Renderiza hijos en un nodo DOM diferente fuera de la raíz del componente. Útil para modales, tooltips, menús desplegables y notificaciones.
Firma de tipo
const Portal: PC<{
root: HTMLElement;
children?: any | any[];
}>Propiedades
| Propiedad | Tipo | Requerido | Descripción |
|---|---|---|---|
root | HTMLElement | Sí | El elemento DOM de destino donde se deben renderizar los hijos. |
children | any | any[] | No | Contenido a renderizar dentro de la raíz del portal. |
Uso básico
import type { PC } from 'preffx';
const Modal: PC = (_, { Portal }) => {
const portalRoot = document.getElementById('modal-root')!;
return (
<Portal root={portalRoot}>
<div class="modal-overlay">
<div class="modal-content">¡Hola desde el portal!</div>
</div>
</Portal>
);
};Contenido reactivo dentro de un portal
Los hijos dentro de un portal son completamente reactivos — las señales los actualizan automáticamente:
const Notification: PC<{ message: string }> = (
{ message },
{ signal, Portal },
) => {
const count = signal(0);
return (
<div>
<Portal root={document.getElementById('toast-root')!}>
<span id="notification-count">{count}</span>
</Portal>
<button onClick={() => (count.value += 1)}>+</button>
</div>
);
};Limpieza
Cuando el componente padre es destruido, Portal limpia automáticamente sus hijos y vacía la raíz del portal mediante root.replaceChildren().
Sin raíz
Si root es null o undefined, Portal no renderiza nada:
<Portal root={null} children={<span>No aparecerá</span>} />
// no renderiza nadaDefer
Muestra un valor de marcador de posición inicial mientras se prepara el valor real (por ejemplo, esperando que se resuelva una Promise, o que una señal reciba un valor que no sea una Promise).
Firma de tipo
const Defer: PC<{
initial?: any;
value: Signal<any>;
}>Propiedades
| Propiedad | Tipo | Requerido | Descripción |
|---|---|---|---|
initial | any | No | Contenido de marcador de posición renderizado mientras value contiene una Promise o aún no está listo. |
value | Signal<any> | Sí | Una señal cuyo valor es observado. Cuando el valor resuelto de la señal no es una Promise, reemplaza el contenido initial. |
Diferir una Promise
import type { PC } from 'preffx';
const SlowData: PC = (_, { signal, Defer }) => {
const data = signal(
new Promise<string>((resolve) =>
setTimeout(() => resolve('¡Cargado!'), 2000)
)
);
return (
<Defer
initial={<div>Cargando...</div>}
value={data}
/>
);
};Secuencia de renderizado:
- Muestra inmediatamente
Cargando... - Después de 2 segundos, cuando la Promise se resuelve, muestra
¡Cargado!
Diferir un valor simple (no Promise)
Si la señal ya contiene un valor simple (no una Promise), Defer lo renderiza inmediatamente — se omite initial:
<Defer
initial="Esperando..."
value={signal('Contenido real')}
/>
// Renderiza inmediatamente "Contenido real"Cambio reactivo de Promise a valor simple
Cuando una señal cambia de una Promise a un valor simple, Defer cambia del marcador de posición al contenido real:
const DynamicDefer: PC = (_, { signal, effect, Defer, onMount }) => {
const value = signal(new Promise(() => {})); // Promise pendiente
onMount(() => {
setTimeout(() => {
value.value = '¡Finalmente resuelto!';
}, 1000);
});
return <Defer initial="Cargando..." value={value} />;
};Array de hijos mediante Defer
Defer funciona con cualquier contenido, incluyendo arrays de nodos JSX:
const items = signal([
<span id="a">Alpha</span>,
<span id="b">Beta</span>,
]);
return (
<Defer
initial={[<div>Cargando...</div>]}
value={items}
/>
);
// Renderiza inmediatamente <span>Alpha</span><span>Beta</span>Limpieza
Cuando el componente padre es destruido, Defer limpia automáticamente su seguimiento interno de señales.