4. SusRouteView — 可视化容器
API
SusRouteView 是核心基础组件 SusScreenOutlet<SusScreen> 的子类——一个与导航无关的 屏幕层容器。它只添加了路由相关的逻辑;挂载 + KeepAlive(LRU)的底层机制都在 core 中实现。
csharp
public class SusRouteView : SusScreenOutlet<SusScreen>
{
public SusRouter Router { get; set; }
// Inherited from SusScreenOutlet<SusScreen>:
public int MaxKeepAlive { get; set; } = 10;
public SusScreen CurrentScreen { get; protected set; }
public void OnRouteChanged(SusRoute fromRoute, SusRoute toRoute);
public void RenderNestedChain(List<SusScreen> chainScreens);
}流程
SusRouter.Navigate(from, to)
|
SusRouteView.OnRouteChanged(from, to):
1. if !KeepAlive → from.Screen.parent.Remove(from.Screen) ← detach + drop
if KeepAlive → Remove(from.Screen) + CacheKeepAliveScreen(key, screen) ← detach + cache
2. Add(to.Screen) ← add new/cached screen
3. to.Screen.style.flexGrow = 1; CurrentScreen = to.Screen过渡动画(PlayIn/PlayOut)由 SusRouter 围绕这次替换来驱动。
KeepAlive —— off-DOM LRU 缓存
csharp
router.Register("/battle/:id", typeof(BattleScreen), new SusRouteConfig
{
KeepAlive = true,
Transition = SusRouteTransition.Fade(0.2f)
});工作原理
KeepAlive 是一个 off-DOM 缓存,而不是一个包装元素——屏幕会从 outlet 中分离 (parent.Remove(screen)),不会用 display:none 隐藏,也不会被包装进 core 的 SusKeepAlive 组件中。
- 离开 KeepAlive 屏幕时:
OnRouteChanged会把该屏幕从 outlet 中移除,并调用CacheKeepAliveScreen(key, screen)。被分离的 VisualElement 子树及其Prop<T>状态都会保留在内存中。fromRoute.Screen会被清空,IsActive = false。 - 返回时: router 会调用
TryGetKeepAliveScreen(key, out screen);命中时, 缓存中的实例会被重新添加回 outlet ——不会创建新实例,也不会调用Left()。 键为SusRouter.KeepAliveKey(route)(回退为route.FullPath,含 query 参数)。 - 在 router 中: 如果目标 KeepAlive 路由已经有一个存活的缓存屏幕,router 会复用它, 而不是重新构造一个。
LRU 淘汰
缓存和顺序都保存在 SusScreenOutlet<TScreen> 中:
csharp
private readonly Dictionary<string, TScreen> _keepAliveCache;
private readonly List<string> _keepAliveOrder; // LRU: front = oldest
public int MaxKeepAlive { get; set; } = 10;当添加一个新键会超出 MaxKeepAlive 时,最老的条目会被优先淘汰:
_keepAliveOrder[0](最老的)从顺序列表中移除;- 执行
OnScreenEvicted(oldScreen)——SusRouteView重写了它,调用screen.Left()(清理); - 该条目从
_keepAliveCache中删除。
ClearKeepAliveCache() 会淘汰所有条目(对每一个都运行清理钩子)。
在 App.sharq 中的用法
xml
<template>
<ui:VisualElement $MainElement class="app">
<sus:SusRouteView ref="RouteView" />
</ui:VisualElement>
</template>
<script>
public SusRouter Router = new();
public SusRouteView RouteView;
public override void Mounted()
{
Router.SetRouteView(RouteView);
RouteView.Router = Router;
Router.Register("/main-menu", typeof(MainMenuScreen));
Router.Register("/battle", typeof(BattleHudScreen),
new SusRouteConfig { KeepAlive = true });
Router.Register("/loading", typeof(LoadingScreen));
Router.Replace("/loading");
}
</script>嵌套路由 —— 嵌套渲染
每个父级屏幕都可以注册一个嵌套的 SusRouteView:
csharp
public class SettingsScreen : SusScreen
{
public SusRouteView ChildRouterView;
protected override void Build()
{
ChildRouterView = new SusRouteView();
RegisterChildView(ChildRouterView);
Add(ChildRouterView);
}
}导航到 /settings/profile 时:
ResolveChain()构建出链:SettingsRecord → ProfileRecord- 根
SusRouteView渲染SettingsScreen SettingsScreen.ChildRouterView渲染ProfileTabScreen
限制
当子路由发生变化(/settings/profile → /settings/account)时,父级会被重建。链级别的 diffing 计划在未来实现。