---
title: "Réactivité — Docs zenit Zig UI"
description: "Stockez les faits dans des Signals, dérivez avec des Memos et synchronisez le monde extérieur avec des Effects."
url: https://zenit.z.express/fr/docs/guide/reactivity
language: fr
alternate_en: https://zenit.z.express/docs/guide/reactivity.md
alternate_zh: https://zenit.z.express/zh/docs/guide/reactivity.md
alternate_es: https://zenit.z.express/es/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_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
---

# Réactivité

Stockez les faits dans des Signals, dérivez avec des Memos et synchronisez le monde extérieur avec des Effects.

## Trois primitives

**Signal**

Une source de vérité modifiable. Lisez avec `get()`, écrivez avec `set(v)` ; la lecture enregistre une dépendance dans le calcul en cours.

**Memo**

Calculé paresseusement à partir d'autres valeurs réactives ; réutilise son dernier résultat tant que les entrées ne changent pas et reste silencieux si la valeur est égale.

**Effect**

Exécute un effet de bord quand ses dépendances changent : mettre à jour un nœud, journaliser, synchroniser un système externe.

## Flux de données de bout en bout

`ui.textFmt` s'abonne aux Signals / Memos que vous lui passez et ne reformate que ce nœud texte quand ils changent. Un Memo reçoit ses dépendances via un struct de contexte explicite.

`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 parcourt les arêtes de dépendance — Signal → Memo → textFmt — et seul le nœud texte abonné est marqué render-dirty.

## Des Signals, pas de diff

zenit conserve un vrai arbre UI pour le layout, le hit-testing et le dessin, mais les mises à jour ne passent pas par « ré-exécuter les composants → construire un arbre candidat → tout comparer ». Les Memos et Effects qui lisent un Signal enregistrent une dépendance ; `set` notifie exactement ces abonnés, qui marquent les nœuds concernés layout- ou render-dirty.

SIGNAL vs DIFF

L'arbre est toujours là ; ce qui disparaît, c'est la reconstruction d'un arbre candidat et la recherche de différences à chaque changement.

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

La vraie Reactive Counter.app : chaque +1 met à jour count et doubled ensemble jusqu'à 3 / 6, puis Reset remet les deux abonnés à zéro.

## Mettre à jour depuis des événements

Les callbacks des composants sont des `HandlerRef`. Conservez avec `cx.bindState` un petit struct qui détient le pointeur du Signal, puis transformez l'une de ses méthodes en callback avec `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);
```

## Lire et s'abonner

| API | Rôle |
| --- | --- |
| `signal.get()` | Lit et enregistre une dépendance dans le Memo / Effect courant |
| `signal.peek()` | Lit sans s'abonner — pratique pour un instantané dans un handler |
| `signal.set(v)` | Écrit et notifie les abonnés |
| `signal.update(fn)` | Calcule la valeur suivante à partir de la précédente, puis écrit |
| `scope.createEffect(ctx, fn)` | Exécute un effet de bord au changement ; libéré avec son 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);
```

## Règles pratiques

-   **Un fait, un Signal.** Ne gardez pas le même état à la fois dans un champ ordinaire et dans un Signal.
    
-   **Les valeurs dérivables sont des Memos.** Ne synchronisez pas à la main des champs dérivés dans chaque handler.
    
-   **Les Effects ne font que synchroniser.** N'écrivez jamais inconditionnellement dans une dépendance de l'Effect lui-même : c'est une boucle.
    
-   **Les durées de vie appartiennent à un Scope.** Quand le Scope d'une page est libéré, ses Signals, Memos et Effects partent avec lui.
    

> TIP
> 
> **Une UI statique n'a pas besoin de réactivité.** Montez directement le texte et la structure qui ne changent jamais. Les primitives réactives servent aux données qui changent — ce n'est pas une enveloppe nécessaire à chaque nœud.
