Skip to content

第 04 章:Home Store 到 Native ViewModel ​

1. Renderer 不是“把 Store 序列化一下” ​

MobileNativeHomeRenderer 同时读取多组 Home hooks:

ts
const session = useHomeSessionState();
const facts = useHomeFacts();
const interaction = useHomeInteraction();
const shell = useHomeShell();
const homeNavigation = useHomeNavigation();
const portfolioSection = useHomeSection('portfolio');
const perpsSection = useHomeSection('perps');
const defiSection = useHomeSection('defi');
const nftSection = useHomeSection('nft');
const historySection = useHomeSection('history');
const marketSection = useHomeSection('market');

这些 hook 的返回值不是直接对应 IHomeContainerItem。它们包含状态 kind、权限、revision、缓存/实时来源、后台请求状态和业务 payload。Renderer 需要先做一次“语义收敛”,再把收敛结果送入 native 协议。

2. 语义状态到可渲染 rows ​

mobileNativeHomeViewModelAdapter.ts 的主函数是 buildMobileNativeHomeViewModelSections。它先处理通用状态:

ts
const state = buildStateSection({ labels, sectionId, semantic });
if (state) return state;

也就是说 loading、error、empty 等状态优先于业务 payload;只有在语义状态允许展示 ready 内容时,才按 tab 进入 Portfolio / Perps / DeFi / NFT / History 分支。

3. Renderer 类型系统承担了三种“翻译” ​

展示翻译 ​

金额、价格、APR、时间、地址和隐私遮罩被转换为字符串。比如 History 会根据 decoded action 区分 approve、function call、send/receive、internal swap 和 UTXO transfer,再生成统一 row。

交互翻译 ​

每个 item 不携带函数,只携带 actionId 和可选 itemId。例如 Portfolio asset row 的 presentation id 可能因网络聚合改变,但 portfolioAssetItemIdByPresentationId 会把展示 id 映射回当前 Store 的业务 token key。

拓扑翻译 ​

Home navigation semantic model 会决定 tab 是 inline 还是 handoff:

ts
const destination =
  tabTopology.destinations?.[tabId] === 'web' ? 'handoff' : 'inline';

handoff tab 没有 sections,只有 handoffCommandId;native 点击它只产生一个 intent,由 JS 决定切到 Web/Perps route。

4. “最后一次已提交 presentation”为什么存在 ​

账户切换或某个 section 刚进入 loading 时,立即用空 sections 替换会造成 native page、slot host、列表高度同时坍缩。Renderer 因此保留多个 last committed 值:

  • lastCommittedPortfolioSectionsRef
  • lastCommittedPortfolioFilterPresentationRef
  • lastCommittedTabTopologyRef

规则是:loading 可以撤销 command,但不要立刻撤销已稳定的几何身份。

例如 Portfolio loading 时:

  1. 保留旧 rows 的 id/顺序,防止 RecyclerView/UITableView 重建;
  2. 清掉旧 owner 的 action command,避免点击旧内容触发错误业务;
  3. 新缓存或 live data 到达后,再用新 payload 重新配置同一组 presentation identity。

测试中明确覆盖了“keep committed portfolio presentation while stripping owner commands”和“stable Market section while owner-scoped cache misses”。

5. Snapshot 的组装顺序 ​

Renderer 的组装顺序很值得记住:

nativeState 只有在 owner 和 snapshot 同时存在时才生成;否则返回 launch surface。这个顺序避免了“有 payload 没 owner”或“有 owner 没可渲染 tab”的半状态。

6. ViewModel 不是业务真相 ​

Native ViewModel 可以被缓存、投影和重建,但它不拥有:

  • 钱包余额的最终 authoritative 值;
  • command 是否当前有效;
  • 当前 account/network 的权限;
  • Web handoff 的导航副作用。

这些仍然由 Store/Controller 决定。Native Home 是一个高性能 projection,而不是第二个钱包状态机。

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