Routage dans l’application
Décrivez les pages sous forme d’enum, exposez la page courante et ses paramètres comme deux Signals, et obtenez précédent / suivant grâce à une pile d’historique de capacité fixe.
Le modèle Router
ui.fx.Router(Page) est un routeur en mémoire piloté par un enum — pas un routeur d’URL web. Il expose deux Signals, current: *Signal(Page) et params: *Signal(RouteParams), et conserve une pile d’historique de 64 entrées au plus.
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;
}Navigation et paramètres
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 copie les octets de la clé et de la valeur dans ses propres tampons : les modifications ultérieures du tampon de l’appelant ne l’affectent pas ; définir une clé existante écrase sa valeur. La capacité est fixe, et un dépassement renvoie une erreur au lieu de tronquer :
max_params = 8error.TooManyParamsmax_key_len = 64error.KeyTooLongmax_value_len = 512error.ValueTooLongnavigate, back et forward écrivent tous dans les deux Signals. canGoBack() / canGoForward() sont de simples lectures, pas des Signals. Pour piloter le routeur depuis un handler, gardez le pointeur dans une struct cx.bindState :
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);Afficher les pages
La page courante est un Signal(Page) ; basculez entre les sous-arbres de page avec ui.Match. Match donne à chaque page un Scope enfant ; lors d’un changement, il dispose d’abord l’ancien Scope (ses Signals, Effects et ressources partent avec lui), puis libère les anciens nœuds et monte la nouvelle page.
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 ne reconstruit que lorsque la valeur de l’enum change : naviguer de .editor vers .editor avec d’autres paramètres garde la page montée. Une page qui dépend des paramètres doit s’abonner elle-même à router.params :
// 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);Règles de l’historique
- ✓
Une nouvelle navigation tronque la branche suivante. Naviguer après back supprime les anciennes entrées forward.
- ✓
Chaque navigate empile. La même page avec d’autres paramètres — voire une cible identique — devient une nouvelle entrée.
- ✓
À 64 entrées, la moitié la plus ancienne disparaît. Avant le 65e empilement, les 32 plus récentes sont conservées et le curseur se décale avec elles.
- ✓
Pas d’URL. Les liens profonds et les schémas d’URL du système doivent être associés à
navigatepar l’application.
Tracer la navigation
Le routeur ne journalise rien de lui-même. Pour déboguer la navigation, écrivez les transitions utiles dans la Console du Cx courant sous un scope router ; la Console de DevTools peut alors filtrer par scope et afficher le niveau et le point d’appel.
router.navigate(.editor, params);
cx.console().scoped("router").debug("navigate -> {s}", .{@tagName(Page.editor)});Consultez DevTools pour l’API complète de la Console.