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
آخر نشاط في المصدر
٢٨ أغسطس ٢٠٢٦ في ٠٦:٣٢
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٦٧٤
التفرعات
٥٨

خيارات التثبيت

يُحدَّد 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: ```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.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. ## 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