Skip to content

第 06 章:Nitro 宿主与 React Slots ​

1. 为什么需要 Surface + Host + Slot 三件套 ​

React Native 的组件树和原生引擎的内部 view 树不天然相同。Home 需要同时满足:

  • 一个占满页面的 native engine;
  • 许多由 React 渲染的复杂内容;
  • React slot 必须出现在原生 header/list/footer 的精确位置;
  • slot 的交互和可访问性必须受当前 owner 控制。

因此 HomeContainer.native.tsx 的返回结构是:

tsx
<HomeContainerSurface>
  <HomeContainerHost />
  <HomeContainerSlot slotKey="header.account-row">...</HomeContainerSlot>
  <HomeContainerSlot slotKey="content.header.portfolio">...</HomeContainerSlot>
</HomeContainerSurface>

Surface 是 children 管理和 hit testing 的物理容器;Host 是 Nitro HybridView;Slot 是可被移动到原生 host 的 React 子树。

2. Nitro contract ​

HomeContainer.nitro.ts 把 native API 缩成:

ts
export interface IHomeContainerNativeProps extends HybridViewProps {
  initialStateJson: string;
  backgroundColor: string;
  debugOverlayEnabled: boolean;
  onRenderError?: (code: string, message: string) => void;
  onIntent?: (intentJson: string) => void;
}

export interface IHomeContainerNativeMethods extends HybridViewMethods {
  setState(stateJson: string): void;
  completeRefresh(requestId: string): void;
  selectTab(tabId: string, animated: boolean): void;
}

Nitrogen autolinking 把 HomeContainer 绑定到 Swift/Kotlin 的 HybridHomeContainer,而 codegenConfig 把 OneKeyHomeContainerSurface 和 OneKeyHomeContainerSlot 绑定到 Fabric component view manager。

3. JS wrapper 的三个关键技巧 ​

稳定 host ​

ts
function getHomeContainerHost() {
  const runtime = globalThis as typeof globalThis & {
    __onekeyHomeContainerHost?: IHomeContainerHost;
  };
  runtime.__onekeyHomeContainerHost ??= createHomeContainerHost();
  return runtime.__onekeyHomeContainerHost;
}

Host 在 runtime 内缓存,避免重复创建 Nitro host 对象。

稳定 callback ​

onRenderError 和 onIntent 使用 ref 保存最新闭包,再用 nitroCallback 暴露稳定函数。这样 native 不需要因为 React callback identity 改变而频繁重绑,但业务仍能拿到最新 state/handler。

首次 JSON 和增量 JSON 分离 ​

首次通过 initialStateJson,后续通过 ref 的 setState。这与原生的“首帧同步、增量异步”策略对齐。

4. Slot flattening ​

slotBundle.slots 是按语义分组的:accountRow、balance、headerActionRow、contentHeaders、contentStates、contentFooters、tabAccessories。wrapper 把它 flatten 成稳定 key:

语义字段slot key
accountRowheader.account-row
balanceheader.balance
headerActionRowheader.action-row
contentHeaders.portfoliocontent.header.portfolio
contentStates.historycontent.state.history
contentFooters.history.historyEndcontent.footer.history.historyEnd
tabAccessories.nfttab.accessory.nft

native engine 只需要知道 key,不需要知道 React component 的类型。

5. Slot ownership 与“presence suppresses fallback” ​

Surface 会把当前出现的 slot key 集合传回 native:

“slot 是否出现”与“slot 是否有权交互”是两个条件:

  • presence 表示 React 正在接管这个 presentation;native 不应在 owner 切换期间突然显示旧 fallback;
  • owner match 决定它是否能响应触摸、是否进入 accessibility tree。

6. 为什么不直接把 slot 当普通 child ​

普通 child 只能跟随 Surface 的 bounds,无法自然出现在 header 内部的 UITableView/RecyclerView row、tab accessory 或 footer host。Surface/Slot 的自定义 mount、layout、hitTest 和 reparenting,实际上把 React 子树嵌入了原生页面内部的多个“投影点”。

这也是该系统最容易出 bug 的地方:布局变化、列表复用、owner 切换和 React unmount 必须同时触发重新挂载。因此 Android 和 iOS 都把 slotLayoutChange/onSlotLayoutChange 作为显式回调,并在列表 attach/detach、动画结束、header 高度改变时再次布局。

基于 app-monorepo 2026-07-31 源码快照整理 · 非官方文档