---
title: "라우팅 — zenit Zig UI 문서"
description: "페이지를 enum으로 표현하고, 현재 페이지와 파라미터를 두 개의 Signal로 노출하며, 고정 용량 히스토리 스택으로 뒤로 / 앞으로 이동을 지원합니다."
url: https://zenit.z.express/ko/docs/advanced/routing
language: ko
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_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으로 표현하고, 현재 페이지와 파라미터를 두 개의 Signal로 노출하며, 고정 용량 히스토리 스택으로 뒤로 / 앞으로 이동을 지원합니다.

## Router 모델

`ui.fx.Router(Page)`는 enum 기반의 인메모리 라우터이며 웹 URL 라우터가 아닙니다. `current: *Signal(Page)`와 `params: *Signal(RouteParams)` 두 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`는 모두 두 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 scheme은 앱이 직접 `navigate`로 매핑해야 합니다.
    

CAPACITY 64

버리기는 일괄로 이루어집니다. 푸시마다 한 개씩 밀어내는 대신 한 번에 용량의 절반을 비웁니다.

## 내비게이션 기록

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/ko/docs/advanced/devtools)를 참고하십시오.

> WARNING
> 
> **서로 다른 두 라우터.** 이 문서 사이트는 브라우저 path router를 사용합니다. zenit 데스크톱 앱의 `ui.fx.Router`는 enum과 Signal로 동작하며 URL 개념이 없습니다.
