Skip to main content

reactor-navigation

Reactor navigation — `UseNavigation` hook, `NavigationHost` renderer, `NavigationView` sidebar, `TabView` tabs, route params, stack operations, page lifecycle, deep linking, transitions, caching. Use when building multi-page apps or wiring sidebar/tab navigation.

ソース情報

リポジトリ
microsoft/microsoft-ui-reactor
ソースの最終更新活動
2026年10月29日 22:11
検出された SKILL.md の言語
英語
スター
816
フォーク
62

インストール方法

デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。

ソースファイルを確認

インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。

SKILL.md を表示中

SKILL.md
ソースの指示 · 読み取り専用プレビュー
name
reactor-navigation
description
Reactor navigation — `UseNavigation` hook, `NavigationHost` renderer, `NavigationView` sidebar, `TabView` tabs, route params, stack operations, page lifecycle, deep linking, transitions, caching. Use when building multi-page apps or wiring sidebar/tab navigation.
# Navigation in Reactor Reactor uses a **stack-based navigation model** with type-safe routes. You define routes as an enum, create a navigation handle with `UseNavigation`, and render the current page with `NavigationHost`. The system manages back/forward stacks, transitions, caching, and deep linking. ## Quick reference | API | Purpose | |-----|---------| | `UseNavigation(Route.Home)` | Create a `NavigationHandle<Route>` (call once at root) | | `UseNavigation<Route>()` | Retrieve the nearest ancestor's handle (child components) | | `NavigationHost(nav, route => ...)` | Render the current page | | `NavigationView([items], content)` | Sidebar navigation with icons | | `TabView(Tab(...), Tab(...))` | Tab-based parallel navigation | | `UseNavigationLifecycle(...)` | Page appear/disappear callbacks | | `UseSystemBackButton(nav, window)` | Wire system back button to nav stack | | `DeepLinkMap<Route>` | Map URI patterns to routes | ## 1. Defining routes Use an enum. Each value is a distinct page: ```csharp enum Route { Home, Settings, Profile, Details } ``` For routes that carry data, use a discriminated-union pattern with records: ```csharp abstract record Route { public record Home : Route; public record Settings : Route; public record Details(int ItemId) : Route; } ``` ## 2. Basic navigation ```csharp class App : Component { public override Element Render() { var nav = UseNavigation(Route.Home); return VStack(12, HStack(8, Button("Home", () => nav.Navigate(Route.Home)), Button("Settings", () => nav.Navigate(Route.Settings)), Button("Back", () => nav.GoBack()).IsEnabled(nav.CanGoBack) ), NavigationHost(nav, route => route switch { Route.Home => Component<HomePage>(), Route.Settings => Component<SettingsPage>(), _ => TextBlock("Not found") }) ); } } ``` **Key points:** - `UseNavigation(Route.Home)` — call once at the root with the initial route. - `nav.Navigate(route)` — pushes onto the back stack. - `nav.GoBack()` / `nav.GoForward()` — stack navigation. - `NavigationHost(nav, route => ...)` — renders the matched element. ## 3. Stack operations | Method | Effect | |--------|--------| | `Navigate(route)` | Push route onto back stack | | `GoBack()` | Pop current, return to previous | | `GoForward()` | Move forward (after going back) | | `Replace(route)` | Swap current route without touching the stack | | `Reset(route)` | Clear all stacks, start fresh | | `PopTo(predicate)` | Pop until a matching route is found | Properties: `CurrentRoute`, `CanGoBack`, `CanGoForward`, `BackStack`, `ForwardStack`, `Depth`. Use `Reset` for sign-out flows to prevent navigating back to authenticated pages. ## 4. NavigationView (sidebar) ```csharp var nav = UseNavigation(Route.Home); return NavigationView( [ NavItem("Home", icon: "Home", tag: "Home"), NavItem("Settings", icon: "Setting", tag: "Settings"), NavItem("Profile", icon: "Contact", tag: "Profile") ], content: NavigationHost(nav, route => route switch { Route.Home => VStack(Heading("Home"), TextBlock("Welcome.")).Padding(24), Route.Settings => VStack(Heading("Settings"), TextBlock("Configure.")).Padding(24), Route.Profile => VStack(Heading("Profile"), TextBlock("Your info.")).Padding(24), _ => TextBlock("Not found").Padding(24) }) ); ``` `NavItem(label, icon?, tag?)` creates sidebar items. Icons are Segoe Fluent Icons names. The `NavigationView` handles selection state. **Manual wiring (without `UseNavigation`):** `SelectedTag` and `OnSelectionChanged` are record properties — use `with { }`. For the settings item: set `IsSettingsVisible = true` and check `args.IsSettingsSelected` (not via tag). Use `.Set(nv => nv.PaneDisplayMode = ...)` for non-record properties. ## 5. TabView (parallel tabs) Unlike stack navigation, tabs keep all content alive simultaneously: ```csharp return TabView( Tab("Documents", VStack(12, TextBlock("Your documents."), Button("New", () => { })).Padding(24)), Tab("Recent", TextBlock("Recently opened.").Padding(24)), Tab("Shared", TextBlock("Shared files.").Padding(24)) ); ``` Use tabs when users switch freely between parallel workspaces. ## 6. Page lifecycle `UseNavigationLifecycle` fires callbacks when a page appears or disappears: Callbacks are scoped to the nearest enclosing `NavigationHost`. Each nested host dispatches lifecycle events and guards for its own navigation stack. ```csharp UseNavigationLifecycle( onNavigatedTo: ctx => LoadData(ctx.PreviousRoute), onNavigatingFrom: ctx => { if (hasUnsavedChanges) ctx.Cancel(); // prevent navigation }, onNavigatedFrom: ctx => SaveDraft() ); ``` | Callback | When | |----------|------| | `onNavigatedTo` | After this page becomes active — fetch data, start timers | | `onNavigatingFrom` | Before leaving — call `ctx.Cancel()` to block navigation | | `onNavigatedFrom` | After leaving — save drafts, stop timers | ## 7. Page transitions Set `Transition` on `NavigationHost`: ```csharp NavigationHost(nav, route => ...) with { Transition = NavigationTransition.DrillIn() } ``` | Transition | Effect | |-----------|--------| | `NavigationTransition.Entrance()` | Slide up + fade in — the default, and what WinUI's `Frame` plays with no transition info | | `NavigationTransition.Slide()` | Slide + fade — vertical unless you pass a direction | | `NavigationTransition.Fade()` | Crossfade | | `NavigationTransition.DrillIn()` | Scale + fade — use for list→detail | | `NavigationTransition.Spring()` | Spring-physics slide | | `NavigationTransition.None` | Instant swap | | `NavigationTransition.Connected(key)` | Native shared-element motion | `NavigationTransition.Default` is an alias for `Entrance()`. Use `Entrance()` when you mean the motion; use `Default` only when you mean "whatever the framework default is". GoBack automatically reverses direction. Transitions run on the compositor thread. ### Shared elements with independent page motion Mark the source and destination with the same `.ConnectedAnimation(key)` and select that key for the navigation: ```csharp nav.Navigate(Route.Details, new NavigateOptions { ConnectedAnimationKey = "device-card", Transition = NavigationTransition.DrillIn(), }); ``` The shared element and page transition run together. `Transition = null` uses the host's default; `NavigationTransition.None` selects shared-element-only motion. `NavigationTransition.Connected(key)` is a shorthand for that latter case. The key and page transition travel together on back/forward navigation. Use a unique key within each active page subtree, including nested hosts. The destination can appear after its first render or effect: navigation waits for its measured bounds before starting native motion. Missing, duplicate, or zero-sized destinations fall back to the selected page transition. Reduced motion completes the navigation immediately. Both motions finish before the old page is cached or unmounted and lifecycle completion callbacks run. Interrupted navigation still delivers the outgoing page's `onNavigatedFrom`; the current destination receives `onNavigatedTo` when its transition completes. ## 8. Deep linking `DeepLinkMap` maps URI patterns to route constructors: ```csharp var map = UseMemo(() => new DeepLinkMap<Route>() .Map("/", _ => Route.Home) .Map("/settings", _ => Route.Settings) .Map("/users/{id:int}/posts/{postId:int}", args => { var userId = args.Get<int>("id"); var postId = args.Get<int>("postId"); return Route.Details; }, backStackFactory: () => new[] { Route.Home })); // Resolve from activation URI: map.Resolve(uri); ``` **Pattern segments:** | Segment | Matches | |---------|---------| | `/literal` | Exact match | | `/{param}` | String capture | | `/{param:int}` | Typed capture (int, long, bool, Guid) | | `/{param?}` | Optional parameter | | `/{**}` | Wildcard — remaining path | | `?key=value` | Query string parameters | `backStackFactory` builds a synthetic back stack so GoBack works after deep-linked entry. ## 9. Page caching ```csharp NavigationHost(nav, route => ...) with { CacheMode = NavigationCacheMode.Enabled, CacheSize = 5 } ``` | Cache mode | Behavior | |-----------|----------| | `Disabled` | Always unmount/remount (default) | | `Enabled` | LRU cache up to `CacheSize` entries | | `Required` | Always cached, never evicted | Caching preserves scroll position, text input, and component state. ## 10. State serialization ```csharp var state = nav.GetState(); // NavigationState<TRoute> snapshot nav.SetState(state); // restore stacks + fire Navigated(Reset) ``` Reactor returns a plain `NavigationState<TRoute>` POCO and lets you pick the storage format. For JSON persistence pair the snapshot with a `JsonSerializerContext` (AOT-safe): ```csharp [JsonSerializable(typeof(NavigationState<AppRoute>))] partial class AppJsonContext : JsonSerializerContext { } var json = JsonSerializer.Serialize(nav.GetState(), AppJsonContext.Default.NavigationStateAppRoute); // later… nav.SetState(JsonSerializer.Deserialize(json, AppJsonContext.Default.NavigationStateAppRoute)!); ``` Use for persisting navigation across app restarts. ## 11. System back button ```csharp UseSystemBackButton(nav, window); ``` Wires the Windows title-bar back button and hardware back button to your navigation stack automatically. ## Critical gotchas 1. **Call `UseNavigation(initial)` once at the root.** Children retrieve the same handle via `UseNavigation<Route>()` (no initial value) through context. 2. **Use enums for routes.** Compile-time safety — can't navigate to a non-existent route. 3. **Use `Reset` for sign-out.** Clears the stack so users can't go back. 4. **DrillIn for list→detail.** Signals hierarchy; pairs with connected animation. 5. **`ctx.Cancel()` in `onNavigatingFrom`** — the only way to guard against unsaved-changes navigation. 6. **Don't forget `backStackFactory` in deep links.** Without it, GoBack does nothing after a deep-linked entry. ## Threading `UseNavigation` mutators are **thread-safe by default** (issue #234). The handle captures the UI thread when it is created, so calling `Navigate`, `GoBack`, `GoForward`, `Replace`, `Reset`, `PopTo`, or `SetState` from a `Task.Run`, a timer, or after `await … ConfigureAwait(false)` auto-marshals the operation back onto the captured dispatcher — no `threadSafe:` flag and no manual `DispatcherQueue.TryEnqueue` needed: ```csharp await Task.Run(async () => { var itemId = await LoadItemIdAsync(); nav.Navigate(new Route.Details(itemId)); // marshals onto the UI thread automatically }); ``` This mirrors the `UseState` / `UseReducer` contract. Two caveats: - **No live dispatcher → loud throw.** Off-thread calls in a headless or unit-test context (no `ReactorApp.UIDispatcher`), or after the dispatcher has shut down, throw `InvalidOperationException` instead of silently corrupting the back/forward stacks. On the UI thread the call runs inline. - **Off-thread `bool` means "scheduled", not "navigated".** When a mutator marshals, its return value reports that the operation was *enqueued*, not that the navigation ultimately succeeded. Don't branch on it from a background thread.
GitHubで見る