87小时从React Native迁移到UIKit:掏空数据层,再反转宿主
内容提要
作者用87小时将Lody iOS从React Native迁移到原生UIKit,删除RN运行时,涉及482个文件。核心思路是先将重UI组件和数据层抽成独立包,再把共享边界从UI下移到无头数据层,由UIKit接管导航宿主,RN屏幕降为叶子视图逐个替换。借助离线截图验证(像素差仅0.00026)保障安全,但仍有4个回归漏网,均出在离线桩与真实运行环境的接缝处。合并后再按依赖分层重构目录。
延伸解读
共享边界下移:从UI到无头数据层
作者最初选择React Native是为了未来Android客户端能复用数据层和UI抽象,但两个变化打破了平衡:编码代理大幅降低原生UI编写成本,以及导航栏等组件在RN中需经三层翻译才能到UIKit,导致bug集中在层间缝隙。最终将跨平台共享边界从UI下移到无头数据层,iOS和未来Android实现同一版本化契约,各自写原生UI。这提示:当原生UI成本足够低时,为省UI而支付抽象税不再划算。
绞杀者模式的反转:先掏空再换宿主
迁移没有从重写屏幕开始,而是先花六周将重UI组件和数据层抽成独立包,使RN只剩路由和五个Context。随后三步锁定:先建立版本化契约并将状态沉入Swift服务,再反转宿主让UIKit接管导航、RN屏幕降为叶子视图,最后移除RN运行时和构建链。关键顺序是每一步为下一步创造物理前提,且全程保持文件路径冻结,使每个提交都是纯实现替换,便于审查和二分定位。
离线验证的裂缝:像素差0.00026仍漏4个回归
作者用离线截图对比将像素差控制在0.00026,但仍有4个回归漏网,全部出在离线桩与真实运行环境的接缝处:测试入口自身无测试、Debug与Release构建配置偏差、离线桩绕过系统模态层级、静态fixture跳过异步销毁竞态。这说明高精度截图验证不能替代对测试入口、构建变体、真实系统交互和异步生命周期的覆盖,设计验证工具时需专门测试这些边界。
合并后按依赖分层重构:先冻结行为再动结构
87小时冲刺期间作者坚持“改行为时不移动文件”,合并后557个Swift文件仍混在旧Expo模块目录。随后三天分三步重构:整体移入LodyKit并切换SPM,按是否import UIKit拆出LodyData(通过LodyHost协议调用平台副作用),最后扁平化项目层级并将UI代码收回App target。依赖图严格单向,下层对UI知之甚少。这种先锁行为再分层的方式降低了重构风险,也便于逐步验证。
Q&A
为什么作者决定从React Native迁移到原生UIKit?
有两个关键原因:一是编码代理大幅降低了原生UI的编写成本,使得为每个平台单独写UI不再昂贵;二是导航栏、工具栏等最终都要落到UIKit,但在RN中需经过三层翻译,导致状态和假设分散,在适配iOS 26玻璃样式、iPad分屏和折叠屏时bug集中在层与层之间的接缝处,修补成本越来越高。
87小时迁移的核心策略是什么?
核心策略是“先掏空,再反转宿主”。先用六周时间将重UI组件(diff渲染、聊天时间线、富文本编辑器)和数据层抽成独立包,使RN只剩路由和上下文;然后依次完成三个锁定:建立版本化契约并将状态下沉到Swift、反转宿主让UIKit接管导航而RN屏幕降为叶子视图、最后移除RN运行时和构建链。
跨平台共享边界从UI下移到无头数据层是什么意思?
原本计划将数据层和UI抽象都放在RN中,由React组件描述界面,iOS和Android只提供原生视图。迁移后,共享边界下移到无头数据层:cloud-runtime运行在离屏WebView中,iOS和未来的Android客户端实现相同的版本化契约,各自用原生写UI。这样共享的是数据运行时而非UI。
离线截图验证的像素差只有0.00026,为什么还有4个回归漏网?
四个回归都出在离线桩与真实运行环境的接缝处:1)测试入口本身没有测试,Debug屏幕在转为UIKit后退化为无导航栏的扁平列表;2)构建配置偏差,Debug场景只在Debug和UI Verify配置下注册,Release构建中长按设置按钮触发未注册路由而崩溃;3)离线桩绕过了系统模态层级,Device Flow登录时SFSafariViewController的关闭只关了Safari,欢迎屏幕卡在下面;4)静态fixture跳过了实时运行时的异步销毁竞争,从推送通知打开会话时,前一个屏幕的延迟deinit关闭了新屏幕刚打开的会话。
迁移后如何重构目录结构?
合并后按依赖分层重构,分三步:1)只移动文件不拆分代码,将所有Swift源码移入单一Swift包LodyKit,依赖从CocoaPods切换到SPM;2)沿import UIKit边界拆分,将Auth、catalog、outbox、数据运行时宿主等抽到LodyData模块,该模块不导入UIKit,平台副作用通过LodyHost协议由App目标实现注入;3)扁平化项目层级,仓库根目录成为Xcode项目根,移除apps/包装,剩余UI代码折回App目标,按Application、Navigation、ViewControllers等组织。
这次迁移有哪些关键经验教训?
关键经验包括:先决定哪一层值得共享,当原生UI成本降低时,将共享边界下移到无头数据运行时并配版本化契约;先抽取重UI原语,再将共享状态下沉出RN,然后反转导航宿主,使原生外壳能托管旧RN屏幕为独立叶子,从而逐个替换和验证;设计验证工具时,要测试测试入口本身、Release构建标志、真实系统模态栈以及实时运行时会话间的异步销毁。