移动端布局¶
移动端应用运行于手机、折叠屏、平板电脑以及可调整大小的 iPad 窗口上。有一条规则让这一点易于管理:下面的每一层各自做一个决策,且不做别的层的决策。功能界面永远不会询问自己处于哪个平台或窗口宽度。


层级结构¶
- 路由层。 根堆栈 就是布局状态:当前选中了什么、推入了什么、哪个 sheet 处于打开状态。旋转、折叠或调整大小会以不同方式渲染同一个堆栈。没有任何地方会记住某个页面曾在哪一列中。每条路由通过一个 preset 声明自己的类型(
pageRoute、sheetRoute、modalRoute、immersiveRoute);preset 负责 detents、圆角、头部外观以及 Android 回退行为。参见 移动端导航。 -
工作区外壳。
features/layout负责放置位置和宽度,基于layout.ts中的宽度类别:- Expanded:侧边栏、内容和检查器三列。
- Medium(宽 600–720pt):侧边栏位于列表旁,并浮动在页面之上。
- Compact(任一边小于 600pt):侧边栏作为根,页面向后推入。
- 当检查器列放不下时,检查器变成 sheet。
工作区标签页、停靠栏和工具栏位于
features/layout/workspace。嵌套在 sheet 或 popover 中的堆栈,以及在自己的路由下渲染的界面,也都来自这里。 -
功能界面。 界面按角色放置内容。它会绘制一个
AppBar(如果是表单 sheet 则用SheetHeader),渲染其页面,用<Inspector>声明检查器面板,并通过showInspector()请求显示它们。外壳决定使用列还是 sheet。如果在这里按宽度或平台来选择路由、组件、头部选项或 inset,正是此结构要防止的 bug。 - 设计系统。 每个面板、页面、sheet 和模态框都有一个
AppBar,在两个平台上遵循相同契约;sheet 或模态框传入onClose。PaneContext告诉栏它位于哪一列中,从而添加该列的控制项,例如侧边栏开关。内容与栏相接的位置由useBarInset决定。界面外观(Chrome)只在这里按平台分支。 - 原生层。 在 iOS 26 上,各列是
UISplitViewController的列,每列各带一个导航栏。在 Android 以及 iPadOS 18–25 上,外壳根据窗口和铰链(hinge)指标,用 JS 绘制相同的列。Android 从不显示原生堆栈头部;每个栏都在组件树内绘制。
平台分支位于第 1、2、4 层,以 .ios 和 .android 文件实现。
陷阱¶
- iPadOS 18–25 运行 JS 列,且 iOS 部署目标为 18。在 JS 列路径中看起来只属于 Android 的代码,在这些 iPad 上仍然生效。
- Android 会渲染
Home两次:一次在侧边栏列中,一次作为内容堆栈的根。侧边栏中的那份副本永远不会是处于聚焦状态的路由,因此以 focus 为门槛的 hooks(例如 Back 处理)还必须接受usePaneRole() === "sidebar"。 - 模态框和全屏页面呈现在各列之上,并且渲染时没有任何 pane role,因此不会获得列级界面装饰(chrome)。
- 在 iOS 上,每个检查器面板都是独立的原生屏幕,并且只有当前活动的一个会被挂载。通过透明度隐藏的面板在窗口中仍保留其原生栏、工具栏和滚动视图,UIKit 会将它们显示在可见面板之上。
- 从检查器 sheet 打开的路由必须通过
InspectorSheetContext等待 sheet 关闭。在 iOS 上,如果 sheet 正在关闭时再在其上方呈现内容,会与它的关闭过程产生竞争(race)。
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 t3/t3code