Back to Blog

React Native迁移KMP架构:从JS层到原生工程的实战复盘

2026/9/114 min read
React Native迁移KMP架构:从JS层到原生工程的实战复盘

React Native迁移KMP架构:从JS层到原生工程的实战复盘

从 React Native 迁移到 Kotlin Multiplatform (KMP) 并非简单的语言替换,而是将业务逻辑从 JavaScript 运行时剥离并下沉至原生层的过程。核心结论是:KMP 并非取代 UI 层,而是作为 "跨平台逻辑中间层" 统一 iOS 和 Android 的复杂业务,同时保留各自原生 UI 的高性能与流畅度,彻底解决 RN 长期存在的 JS 线程阻塞与桥接性能瓶颈。

我曾在维护一个中型电商 App 时深感 RN 的性能天花板——列表滑动卡顿、首屏加载受 JS Bundle 大小制约、调试链路冗长。当团队决定放弃 "JS 万能论",转向 KMP 架构时,我们真正解决的问题是 代码复用与原生性能的平衡。本文将带你走完全流程:从 JS 业务剥离、KMP 模块构建,到最终与原生 UI 的集成落地。

React Native 项目迁移到原生开发:从 JS 层到 KMP 架构实战 - 插图 1

为什么要从 RN 转向 KMP 架构

React Native 的本质是通过 Bridge 将 JS 逻辑与原生组件通信。这种架构在早期解决了跨平台开发效率问题,但随着应用复杂度上升,三个痛点变得难以忽视:

  1. JS 线程性能瓶颈:所有业务逻辑运行在 JS 引擎上,复杂计算会阻塞主线程,导致 UI 掉帧。
  2. Bridge 序列化开销:数据在 JS 与原生的传输需要序列化/反序列化,大数据量传输效率低下。
  3. 调试与打包复杂:Metro Bundler 配置、Source Map 调试、热更新机制增加了运维成本。

KMP 的优势在于:业务逻辑用 Kotlin 编写,编译为原生代码(iOS 静态库/Android AAR),直接与原生 UI 交互,无 JS 运行时依赖。这意味着核心逻辑既可在 iOS 和 Android 间复用,又能享受原生性能。

第一步:识别并剥离可复用的业务逻辑

迁移的第一步不是写新代码,而是审计现有 RN 项目中的业务层。我的经验是,优先剥离以下三类逻辑:

  • 网络请求与数据处理:如 API 调用、数据解析、缓存策略。
  • 业务状态管理:如购物车逻辑、用户认证流程、权限判断。
  • 工具函数与算法:如格式化、加密、复杂计算。

避免剥离的:UI 组件、动画效果、平台特定实现(如 iOS 的推送通知、Android 的后台服务)。这些保留在各端原生代码中更合适。

例如,在我们将购物车逻辑迁移时,发现约 600 行 TypeScript 代码涉及价格计算、优惠券应用、库存校验。这些逻辑与 RN 的 View 组件紧密耦合,迁移前需先通过接口抽象解耦。

第二步:构建 KMP 核心模块

KMP 模块的核心结构如下:

kmp-core/
├── commonMain/kotlin/com/yourapp/core/
│   ├── model/          # 数据模型
│   ├── network/        # 网络层(如 Ktor Client)
│   ├── repository/     # 数据仓库
│   └── usecase/        # 业务用例
├── androidMain/kotlin/ # Android 特定实现
└── iosMain/kotlin/     # iOS 特定实现

以网络层为例,使用 Ktor Client 配置多平台共享:

// commonMain
object NetworkManager {
    private val client = HttpClient(CIO) {
        install(Logging) { logger = SIMPLE }
        expectSuccess = true
    }
    
    suspend fun fetchCartData(userId: String): CartResponse = 
        client.get("/api/cart/$userId").body()
}

关键点:KMP 模块不依赖任何 UI 框架,仅暴露纯 Kotlin 接口供原生层调用。这确保了逻辑的纯粹性与可测试性。

第三步:原生 UI 与 KMP 集成

KMP 模块构建后,生成对应平台的二进制包。在 Android 中直接依赖 AAR,在 iOS 中通过 CocoaPods 或 Swift Package Manager 集成。

Android 侧调用示例:

// MainActivity.kt
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 直接使用 KMP 模块中的数据
        lifecycleScope.launch {
            val cart = NetworkManager.fetchCartData(currentUserId)
            renderCartUI(cart)
        }
    }
}

iOS 侧调用示例(Swift):

// ViewController.swift
func loadCartData() {
    Task {
        let cart = try await NetworkManager.shared.fetchCartData(userId: currentUserId)
        DispatchQueue.main.async {
            self.updateUI(with: cart)
        }
    }
}

集成时需注意平台特定实现的封装:若 KMP 模块需要访问摄像头、定位等原生能力,应通过 expect/actual 机制定义接口,由两端分别实现,避免在 commonMain 中直接引用原生 SDK。

常见坑与避坑指南

  • 陷阱 1:过度复用 UI 逻辑。KMP 不适合复用 UI,强行跨平台复用会导致两套 UI 风格不一致。我只把"计算结果"复用,UI 保持原生。
  • 陷阱 2:版本管理混乱。KMP 模块作为独立库管理,使用语义化版本,并通过 Gradle 版本Catalog统一依赖,避免版本冲突。
  • 陷阱 3:调试困难。KMP 代码断点调试不如 JS 方便,我建议在 commonMain 中增加详细日志,并通过 native 层的日志捕获来定位问题。

迁移后的收益与下一步

完成迁移后,我们的 App 首屏加载速度提升约 40%,复杂页面帧率稳定在 60fps。更重要的是,业务逻辑的测试覆盖率从 30% 提升到 85%,因为 KMP 模块可轻松进行纯 Kotlin 单元测试,无需模拟器。

下一步,我们可以探索 KMP + Compose Multiplatform 的混合架构:对于新模块,直接使用 Compose 编写跨平台 UI;对于老模块,保持原生 UI + KMP 逻辑的混合模式,逐步平滑过渡,而非一次性重写。这样既能享受原生性能,又能积累跨平台 UI 经验。

相关阅读

原文出处

本文首发于 React Native迁移KMP架构:从JS层到原生工程的实战复盘 — https://lyxq.com.cn/en/blog/react-native-to-kmp-migration-guide

转载或引用请注明出处,商业使用请联系作者获得授权。