---
title: "Routing — zenit Zig UI Doku"
description: "Beschreiben Sie Seiten als Enum, stellen Sie die aktuelle Seite und ihre Parameter als zwei Signals bereit und erhalten Sie Zurück / Vor über einen…"
url: https://zenit.z.express/de/docs/advanced/routing
language: de
alternate_en: https://zenit.z.express/docs/advanced/routing.md
alternate_zh: https://zenit.z.express/zh/docs/advanced/routing.md
alternate_es: https://zenit.z.express/es/docs/advanced/routing.md
alternate_ja: https://zenit.z.express/ja/docs/advanced/routing.md
alternate_ko: https://zenit.z.express/ko/docs/advanced/routing.md
alternate_fr: https://zenit.z.express/fr/docs/advanced/routing.md
project: zenit v0.1.0-alpha (Zig 0.15.2, macOS)
source: https://github.com/version-next/zenit
---

# In-App-Routing

Beschreiben Sie Seiten als Enum, stellen Sie die aktuelle Seite und ihre Parameter als zwei Signals bereit und erhalten Sie Zurück / Vor über einen Verlaufsstapel mit fester Kapazität.

## Das Router-Modell

`ui.fx.Router(Page)` ist ein Enum-gesteuerter In-Memory-Router — kein Web-URL-Router. Er stellt zwei Signals bereit, `current: *Signal(Page)` und `params: *Signal(RouteParams)`, und führt einen Verlaufsstapel mit bis zu 64 Einträgen.

`app.zig`

```zig
const std = @import("std");
const ui = @import("ui");

const Page = enum { welcome, editor, settings };
const Router = ui.fx.Router(Page);

fn mountApp(cx: *ui.Cx, scope: *ui.Scope) anyerror!*ui.Node {
    // The router's two Signals are created in `scope`, so pass a scope that
    // lives as long as the window. The router value itself is large (~290 KB of
    // inline history), so keep it in Cx-owned state and pass the pointer around.
    const router = try cx.bindState(Router, try Router.init(scope, .welcome));

    const shell = try ui.box(cx, .{
        .width = .fill(),
        .height = .fill(),
        .direction = .column,
    }, .{});
    // ...append a toolbar and the page host (see "Rendering pages")
    _ = router;
    return shell;
}
```

> NOTE
> 
> **Der Router ist ein großer Wert.** Der Verlauf ist ein Inline-Array: 64 Einträge, jeder mit einem `RouteParams` mit bis zu 8 Parametern in Inline-Puffern (64-Byte-Key + 512-Byte-Value) — insgesamt etwa 290 KB. Legen Sie ihn in langlebigem Speicher wie `cx.bindState` ab und übergeben Sie einen Zeiger; kopieren Sie ihn nicht als Wert.

## Navigation und Parameter

`navigate.zig`

```zig
var params: ui.fx.router.RouteParams = .{};
try params.set("file", "draft.md"); // copies the bytes; errors on overflow

router.navigate(.editor, params); // page + params
router.navigateTo(.settings); // page, empty params

_ = router.back(); // false when already at the oldest entry
_ = router.forward(); // false when there is no forward branch

if (router.canGoBack()) {
    // e.g. enable a Back button
}
```

`RouteParams.set` kopiert die Key- und Value-Bytes in eigene Puffer, spätere Änderungen am Puffer des Aufrufers wirken sich also nicht aus; ein vorhandener Key wird überschrieben. Die Kapazität ist fest, und ein Überlauf liefert einen Fehler statt abzuschneiden:

| Grenze | Wert | Bei Überlauf |
| --- | --- | --- |
| Parameter pro Eintrag | `max_params = 8` | `error.TooManyParams` |
| Key-Länge | `max_key_len = 64` | `error.KeyTooLong` |
| Value-Länge | `max_value_len = 512` | `error.ValueTooLong` |

`navigate`, `back` und `forward` schreiben jeweils beide Signals. `canGoBack()` / `canGoForward()` sind einfache Lesezugriffe, keine Signals. Um den Router aus einem Handler zu steuern, halten Sie den Zeiger in einem `cx.bindState`\-Struct:

`back_button.zig`

```zig
const Nav = struct {
    router: *Router,

    fn goBack(self: *Nav) void {
        _ = self.router.back();
    }
};

const nav = try cx.bindState(Nav, .{ .router = router });
const back_button = try ui.widgets.Button(.{
    .label = "Back",
    .on_click = cx.on(Nav, nav, Nav.goBack),
}).mount(scope, cx);
```

## Seiten rendern

Die aktuelle Seite ist ein `Signal(Page)`; wechseln Sie die Seiten-Teilbäume darauf mit `ui.Match`. Match gibt jeder Seite einen Kind-Scope; bei einem Wechsel wird zuerst der alte Scope per dispose freigegeben (seine Signals, Effects und Ressourcen gleich mit), dann werden die alten Nodes freigegeben und die neue Seite gemountet.

`pages.zig`

```zig
const page_host = try ui.box(cx, .{
    .width = .fill(),
    .height = .fill(),
}, .{});

// Match appends the page to page_host and rebuilds it when router.current
// changes; each page gets a fresh child Scope that is disposed on leave.
_ = try ui.Match(Page, scope, page_host, router.current, cx, struct {
    fn build(child_scope: *ui.Scope, c: *ui.Cx, page: Page) anyerror!*ui.Node {
        return switch (page) {
            .welcome => mountWelcome(c, child_scope),
            .editor => mountEditor(c, child_scope),
            .settings => mountSettings(c, child_scope),
        };
    }
}.build);
```

Match baut nur neu auf, wenn sich der Enum-Wert ändert: Eine Navigation von `.editor` zu `.editor` mit anderen Parametern lässt die Seite gemountet. Eine Seite, die von Parametern abhängt, sollte `router.params` selbst abonnieren:

`editor_page.zig`

```zig
// editor -> editor with other params does not rebuild the page,
// so subscribe to the params Signal inside the page.
try scope.createEffect(.{ .params = router.params }, struct {
    fn run(ctx: anytype) void {
        // get() returns a copy; keep it in a local so the slices stay valid.
        const params = ctx.params.get();
        const file = params.get("file") orelse "untitled.md";
        std.log.info("editor shows {s}", .{file});
    }
}.run);
```

## Verlaufsregeln

HISTORY STACK

back / forward verschieben nur den Cursor; ein navigate nach back kürzt zuerst history\_len auf den Cursor und legt dann den neuen Eintrag ab.

-   **Neue Navigation kappt den Vorwärtszweig.** Ein navigate nach back verwirft die alten forward-Einträge.
    
-   **Jedes navigate legt ab.** Dieselbe Seite mit anderen Parametern — oder sogar ein identisches Ziel — wird ein neuer Eintrag.
    
-   **Bei 64 Einträgen fällt die ältere Hälfte weg.** Vor dem 65. Ablegen bleiben die neuesten 32 erhalten, und der Cursor verschiebt sich mit.
    
-   **Keine URLs.** Deep Links und System-URL-Schemes muss die App selbst auf `navigate` abbilden.
    

CAPACITY 64

Das Verwerfen erfolgt gebündelt: Statt pro Ablegen einen Eintrag zu verdrängen, wird auf einmal die halbe Kapazität frei.

## Navigation protokollieren

Der Router loggt nicht von selbst. Um die Navigation zu debuggen, schreiben Sie die relevanten Übergänge unter einem `router`\-Scope in die Console des aktuellen Cx; die DevTools Console kann dann nach Scope filtern und Level sowie Aufrufstelle anzeigen.

`trace.zig`

```zig
router.navigate(.editor, params);
cx.console().scoped("router").debug("navigate -> {s}", .{@tagName(Page.editor)});
```

Die vollständige Console-API finden Sie unter [DevTools](https://zenit.z.express/de/docs/advanced/devtools).

> WARNING
> 
> **Zwei verschiedene Router.** Diese Doku-Website nutzt einen Browser-Path-Router; der `ui.fx.Router` einer zenit-Desktop-App arbeitet mit Enums und Signals und kennt keine URLs.
