第 04 章:Home Store 到 Native ViewModel
1. Renderer 不是“把 Store 序列化一下”
MobileNativeHomeRenderer 同时读取多组 Home hooks:
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。它先处理通用状态:
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:
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 值:
lastCommittedPortfolioSectionsReflastCommittedPortfolioFilterPresentationReflastCommittedTabTopologyRef
规则是:loading 可以撤销 command,但不要立刻撤销已稳定的几何身份。
例如 Portfolio loading 时:
- 保留旧 rows 的 id/顺序,防止 RecyclerView/UITableView 重建;
- 清掉旧 owner 的 action command,避免点击旧内容触发错误业务;
- 新缓存或 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,而不是第二个钱包状态机。