---
title: "Reactividad — Docs de zenit Zig UI"
description: "Guarda los hechos en Signals, deriva con Memos y sincroniza el mundo exterior con Effects."
url: https://zenit.z.express/es/docs/guide/reactivity
language: es
alternate_en: https://zenit.z.express/docs/guide/reactivity.md
alternate_zh: https://zenit.z.express/zh/docs/guide/reactivity.md
alternate_ja: https://zenit.z.express/ja/docs/guide/reactivity.md
alternate_ko: https://zenit.z.express/ko/docs/guide/reactivity.md
alternate_fr: https://zenit.z.express/fr/docs/guide/reactivity.md
alternate_de: https://zenit.z.express/de/docs/guide/reactivity.md
project: zenit v0.1.0-alpha (Zig 0.15.2, macOS)
source: https://github.com/version-next/zenit
---

# Reactividad

Guarda los hechos en Signals, deriva con Memos y sincroniza el mundo exterior con Effects.

## Tres primitivas

**Signal**

Una fuente de verdad escribible. Lee con `get()`, escribe con `set(v)`; leer registra una dependencia en el cálculo actual.

**Memo**

Se calcula de forma perezosa a partir de otros valores reactivos; reutiliza su último resultado mientras las entradas no cambian y no notifica nada si el valor resulta igual.

**Effect**

Ejecuta un efecto secundario cuando cambian sus dependencias: actualizar un nodo, escribir un log, sincronizar un sistema externo.

## Flujo de datos completo

`ui.textFmt` se suscribe a los Signals / Memos que le pasas y vuelve a formatear solo ese nodo de texto cuando cambian. Un Memo recibe sus dependencias a través de un struct de contexto explícito.

`counter.zig`

```zig
const count = try scope.createSignal(u32, 0);

const doubled = try scope.createMemo(u32, .{ .count = count }, struct {
    fn compute(ctx: anytype) u32 {
        return ctx.count.get() * 2;
    }
}.compute);

try root.appendChild(cx.allocator, try ui.textFmt(
    cx,
    scope,
    "count = {d}, doubled = {d}",
    .{ count, doubled },
    .{ .color = cx.tokens.color.fg_primary },
));
```

DATA FLOW

Un set recorre las aristas de dependencia —Signal → Memo → textFmt— y solo el nodo de texto suscrito se marca render-dirty.

## Signals, no diffing

zenit mantiene un árbol de UI real para layout, hit-testing y dibujo, pero las actualizaciones no siguen el camino “volver a ejecutar componentes → construir un árbol candidato → hacer diff de todo”. Los Memos y Effects que leen un Signal registran una dependencia; `set` notifica exactamente a esos suscriptores, que marcan los nodos afectados como layout- o render-dirty.

SIGNAL vs DIFF

El árbol sigue ahí; lo que desaparece es reconstruir un árbol candidato y buscar diferencias en cada cambio.

[Video](https://zenit.z.express/media/reactive-counter.mp4?v=635ee40b41)

La Reactive Counter.app real: cada +1 actualiza count y doubled a la vez hasta 3 / 6, y luego Reset pone a cero ambos suscriptores.

## Actualizar desde eventos

Los callbacks de los componentes son `HandlerRef`. Guarda con `cx.bindState` un pequeño struct que tenga el puntero al Signal y convierte uno de sus métodos en callback con `cx.on`.

`handlers.zig`

```zig
const Bindings = struct {
    count: *ui.Signal(u32),

    fn increment(self: *Bindings) void {
        self.count.set(self.count.get() + 1);
    }
};

const bindings = try cx.bindState(Bindings, .{ .count = count });
const on_click = cx.on(Bindings, bindings, Bindings.increment);

const plus = try ui.widgets.Button(.{
    .label = "+1",
    .on_click = on_click,
}).mount(scope, cx);
```

## Leer y suscribirse

| API | Qué hace |
| --- | --- |
| `signal.get()` | Lee y registra una dependencia en el Memo / Effect actual |
| `signal.peek()` | Lee sin suscribirse; útil para instantáneas dentro de handlers |
| `signal.set(v)` | Escribe y notifica a los suscriptores |
| `signal.update(fn)` | Calcula el siguiente valor a partir del anterior y lo escribe |
| `scope.createEffect(ctx, fn)` | Ejecuta un efecto secundario al cambiar; se libera con su Scope |

`effects.zig`

```zig
// Effect: sync a value to something outside the reactive graph.
try scope.createEffect(.{ .count = count }, struct {
    fn run(ctx: anytype) void {
        std.log.info("count is now {d}", .{ctx.count.get()});
    }
}.run);

// peek(): read without subscribing (no dependency is recorded).
const snapshot = count.peek();

// update(): read-modify-write in one call.
count.update(struct {
    fn inc(v: u32) u32 {
        return v + 1;
    }
}.inc);
```

## Reglas prácticas

-   **Un hecho, un Signal.** No guardes el mismo estado a la vez en un campo normal y en un Signal.
    
-   **Los valores derivables son Memos.** No sincronices a mano campos derivados en cada handler.
    
-   **Los Effects solo sincronizan.** Nunca escribas incondicionalmente en una dependencia del propio Effect: eso es un bucle.
    
-   **Los ciclos de vida pertenecen a un Scope.** Cuando se libera el Scope de una página, sus Signals, Memos y Effects se van con él.
    

> TIP
> 
> **La UI estática no necesita reactividad.** Monta directamente el texto y la estructura que nunca cambian. Las primitivas reactivas resuelven datos que cambian; no son un envoltorio que necesite cada nodo.
