---
title: "ルーティング — zenit Zig UI ドキュメント"
description: "ページを enum で表し、現在のページとパラメーターを 2 つの Signal として公開し、固定容量の履歴スタックで戻る / 進むを実現します。"
url: https://zenit.z.express/ja/docs/advanced/routing
language: ja
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_ko: https://zenit.z.express/ko/docs/advanced/routing.md
alternate_fr: https://zenit.z.express/fr/docs/advanced/routing.md
alternate_de: https://zenit.z.express/de/docs/advanced/routing.md
project: zenit v0.1.0-alpha (Zig 0.15.2, macOS)
source: https://github.com/version-next/zenit
---

# アプリ内ルーティング

ページを enum で表し、現在のページとパラメーターを 2 つの Signal として公開し、固定容量の履歴スタックで戻る / 進むを実現します。

## Router のモデル

`ui.fx.Router(Page)` は enum 駆動のインメモリルーターで、Web の URL ルーターではありません。`current: *Signal(Page)` と `params: *Signal(RouteParams)` の 2 つの Signal を公開し、内部で最大 64 件の履歴スタックを保持します。

`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
> 
> **Router は大きな値です。** 履歴はインライン配列です。64 件のエントリがそれぞれ最大 8 個のパラメーターを持つ `RouteParams`（key 64 バイト + value 512 バイトのインラインバッファ）を抱え、合計約 290 KB になります。`cx.bindState` などの長寿命ストレージに置き、ポインターで渡してください。値でコピーしないでください。

## ナビゲーションとパラメーター

`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` は key と value のバイト列を自身のバッファにコピーするため、呼び出し側のバッファが後で変わっても影響しません。既存の key を設定すると値が上書きされます。容量は固定で、あふれた場合は切り詰めずにエラーを返します。

| 制限 | 値 | 超過時 |
| --- | --- | --- |
| エントリあたりのパラメーター数 | `max_params = 8` | `error.TooManyParams` |
| key の長さ | `max_key_len = 64` | `error.KeyTooLong` |
| value の長さ | `max_value_len = 512` | `error.ValueTooLong` |

`navigate`、`back`、`forward` はいずれも 2 つの Signal に書き込みます。`canGoBack()` / `canGoForward()` は通常の読み取りで、Signal ではありません。ハンドラーから Router を操作するには、ポインターを `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);
```

## ページの描画

現在のページは `Signal(Page)` です。`ui.Match` で enum 値に応じてページのサブツリーを切り替えます。Match はページごとに子 Scope を作成し、切り替え時にはまず古い Scope を dispose し（その中の Signal、Effect、リソースもまとめて解放）、次に古いノードを解放して新しいページをマウントします。

`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 は enum 値が変わったときだけ再構築します。`.editor` から別のパラメーター付きの `.editor` へ移動してもページは再マウントされません。パラメーターに依存するページは、自分で `router.params` を購読してください。

`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);
```

## 履歴のルール

HISTORY STACK

back / forward はカーソルを動かすだけです。back の後の navigate は、まず history\_len をカーソル位置で切り詰めてから新しいエントリをプッシュします。

-   **新しいナビゲーションは進む側の分岐を切り捨てます。**back の後に navigate すると、以前の forward エントリは破棄されます。
    
-   **navigate は毎回プッシュします。**同じページでパラメーターが違う場合も、まったく同じ行き先でも、新しい履歴エントリになります。
    
-   **64 件に達すると古い半分を破棄します。**65 回目のプッシュの前に最新の 32 件を残し、カーソルもそれに合わせて移動します。
    
-   **URL はありません。**ディープリンクやシステムの URL スキームは、アプリ側で `navigate` に対応付ける必要があります。
    

CAPACITY 64

破棄はまとめて行われます。プッシュのたびに 1 件ずつ追い出すのではなく、一度に容量の半分を空けます。

## ナビゲーションの記録

Router 自体はログを出力しません。ナビゲーションを調べるときは、重要な遷移を現在の Cx の Console に `router` scope 付きで書き出してください。DevTools Console で scope ごとに絞り込み、レベルと呼び出し位置を確認できます。

`trace.zig`

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

Console の API 全体は [DevTools](https://zenit.z.express/ja/docs/advanced/devtools) を参照してください。

> WARNING
> 
> **2 種類のルーター。** このドキュメントサイトはブラウザの path router を使っています。zenit デスクトップアプリの `ui.fx.Router` は enum と Signal で動作し、URL の概念を持ちません。
