第 14 章:设计取舍与风险地图
1. 这套设计最值得学习的地方
取舍一:native engine 只吃 projection
原生代码处理数千行布局与交互,但不理解钱包业务。它接收 renderer、actionId、selectedTabId 等通用字段;业务语义留在 JS,避免 Swift/Kotlin 各自复制一套钱包 domain model。
取舍二:React slot 保留复杂交互
账号选择、余额、WalletActions、空状态、筛选器等仍由 React/Tamagui/kit 负责。原生 engine 只提供 host 和滚动性能。这让业务 UI 可以复用现有组件,又能把长列表和滚动惯性下沉。
取舍三:loading 不等于清空
lastCommitted*、cached snapshot、stable row identity 和 slot geometry 一起解决了“数据还没 ready,但页面不能跳”的问题。这里的 loading 是一种 presentation transition,而非简单 boolean。
取舍四:安全检查分层重复
TS、Store、Surface/native slot、Swift/Kotlin decoder 都会检查一部分 owner/invariant。重复不是浪费,而是跨语言边界上的 defense in depth。
2. 复杂度换来了什么
收益主要来自首屏和滚动性能;成本主要来自跨语言协议、slot reparenting、nested scrolling 和状态时序。最不应该做的简化,是删掉 owner/revision/invariant;它们看起来像样板代码,却正是防止数据错位的边界。
3. 风险地图
| 风险 | 触发条件 | 现有缓解 | 修改时应关注 |
|---|---|---|---|
| 旧账户内容串入新账户 | owner 切换时异步/slot 延迟 | epoch、sessionId、ownsSlot、viewport reset | 所有缓存和 callback 是否带 owner |
| tab 视觉与 Store 不一致 | pager 先于 Store settle | pending transition、authority revision、revert | 不要在 native 直接确认为真 |
| slot 覆盖错误位置 | list attach/detach、dynamic type | host lookup、layout change、parking | host size 0 时不能硬挂 |
| 首屏空白/跳动 | initial state 太早或缓存不完整 | launch surface、sync initial path、critical chunk | 不要把任意 JSON 当 cache hit |
| 原生协议漂移 | TS/Swift/Kotlin 独立修改 | shared fixture + contracts | 先改 fixture/validator 再改 renderer |
| AllNetworks 部分成功污染 | 网络响应缺失/旧 owner | authoritative response count | 区分 partial data 与 success |
| 快照不断膨胀 | 高频 Store commit | projection allowlist、chunk signature、coalesce | 不要直接 dump live payload |
4. 如果要继续演进
优先级建议:
- 把协议 schema/fixture 生成与 TS/Swift/Kotlin contract 更紧地绑定,减少手工同步;
- 为 slot host 和 collapse offset 增加跨平台可视化调试工具;
- 明确 cache snapshot 的“可展示字段”与“不可执行字段”边界,持续审计新增字段;
- 把 Android/iOS 共用的行为模型(tab transition、owner、refresh、slot authorization)抽成更明确的测试向量;
- 在性能数据充分后,再评估是否扩大 native renderer 覆盖范围,而不是先把更多业务逻辑搬进 Swift/Kotlin。
5. 最终模型
text
Home Store = 业务真相
ViewModel = 展示投影
State protocol = 跨语言数据合同
Native engine = 高性能几何/手势执行器
React slots = 复杂业务 UI 的回挂点
Intent + authority = 业务副作用的唯一回流通道
Owner/session = 防止旧上下文污染的贯穿式身份
Snapshot cache = 冷启动展示种子,不是 live truth这比“React Native + 一段原生列表”更准确:它是一个围绕首屏、滚动、跨平台和一致性约束搭出的页面运行时。