Signals
Los signals son las primitivas reactivas de EmberKit. Almacenan estado y notifican a los suscriptores cuando cambian los valores — sin re-renderizar toda la página.
Crear un signal
import { createSignal } from '@emberkit/core';
const [count, setCount] = createSignal(0);
// Leer el valor
console.log(count()); // 0
// Actualizar el valor
setCount(1);
console.log(count()); // 1
Un signal devuelve una tupla: una función getter y una función setter.
Actualizar signals
// Asignar un valor estático
setCount(5);
// Actualizar según el valor anterior (sintaxis de updater con función)
setCount((prev) => prev + 1);
Opciones de signal
const [name, setName] = createSignal('Alice', {
equals: (prev, next) => prev === next, // comprobación de igualdad personalizada
});
Suscribirse a cambios
Cada signal tiene un método subscribe que registra un callback, ejecutado cuando cambia el valor. El callback recibe el nuevo valor.
const [count, setCount] = createSignal(0);
const unsub = count.subscribe((newVal) => {
console.log('count changed to', newVal);
});
setCount(5); // logs "count changed to 5"
// Limpiar la suscripción cuando termines
unsub();
Cómo funciona
La hidratación usa subscribe para actualizar nodos DOM enlazados. Consulta Hidratación.
Valores computados con createMemo
createMemo deriva un valor a partir de otros signals. Solo recalcula cuando cambian las dependencias:
import { createSignal, createMemo } from '@emberkit/core';
const [count, setCount] = createSignal(0);
const doubled = createMemo(() => count() * 2);
console.log(doubled.value); // 0
setCount(3);
console.log(doubled.value); // 6
Efectos con createEffect
0.8.0+
createEffect y createMemo ahora rastrean dependencias de signals y se re-ejecutan cuando esos signals cambian (con soporte de batch / untrack). Los effects no se ejecutan durante SSR.
Los effects ejecutan efectos secundarios cuando cambian los signals:
import { createSignal, createEffect } from '@emberkit/core';
const [theme, setTheme] = createSignal('light');
createEffect(() => {
document.body.className = theme();
});
// Más tarde: dispara el effect
setTheme('dark');
Los effects pueden devolver una función de limpieza:
createEffect(() => {
const handler = () => console.log('resize');
window.addEventListener('resize', handler);
return () => {
window.removeEventListener('resize', handler);
};
});
Actualizaciones en batch
batch agrupa múltiples actualizaciones de signals en una sola notificación:
import { createSignal, batch } from '@emberkit/core';
const [firstName, setFirstName] = createSignal('John');
const [lastName, setLastName] = createSignal('Doe');
batch(() => {
setFirstName('Jane');
setLastName('Smith');
});
// Solo se envía una notificación
Untracking
untrack lee un signal sin rastrearlo como dependencia:
const [count, setCount] = createSignal(0);
createEffect(() => {
console.log(count());
untrack(() => {
console.log(count());
});
});
Enlace al DOM (signals → DOM)
Los signals se combinan con data-ek-bind para que las actualizaciones lleguen a nodos concretos en lugar de re-renderizar el árbol:
const [count, setCount] = createSignal(0);
return <span data-ek-bind={count}>{count()}</span>;
Todos los atributos de enlace (data-ek-show, data-ek-hide, data-ek-show-when, …) están documentados en Hidratación.
Componentes que aceptan signals
Los componentes pueden detectar props signal y cablear bindings internamente. Pasa un signal o un booleano simple para salida solo SSR:
<Modal open={open} onClose={() => setOpen(false)} />
<Modal open={true} />
Consulta Componentes.
Ejemplo completo
import { createSignal, createMemo, createEffect } from '@emberkit/core';
function Counter() {
const [count, setCount] = createSignal(0);
const doubled = createMemo(() => count() * 2);
const isEven = createMemo(() => count() % 2 === 0);
createEffect(() => {
document.title = `Count: ${count()}`;
});
return (
<div>
<p>Count: {count()}</p>
<p>Doubled: {doubled.value}</p>
<p>{isEven.value ? 'Even' : 'Odd'}</p>
<button type="button" onClick={() => setCount((c) => c + 1)}>Increment</button>
<button type="button" onClick={() => setCount(0)}>Reset</button>
<span data-ek-bind={count}>{count()}</span>
</div>
);
}
Próximos pasos
- Hidratación — Referencia de
data-ek-bind - Context — Compartir signals entre componentes
- Componentes — Componentes estáticos vs interactivos
- Componentes UI — Demos de Modal, Tabs, Counter