你敢信吗?同样是跨端,KMP 跟 RN 居然差这么多!

不知道你们在做跨平台混合开发即原生Android+原生iOS+跨平台lib时,是如何选择的。Kotlin Multiplatform 和 React Native 都能减少重复代码,但它们把共享层放在了完全不同的位置。

前者通常保留两端原生 UI,把领域逻辑、数据访问和网络层放进 commonMain;后者把大部分界面和交互交给 JavaScript/TypeScript,再通过原生模块接入设备能力。选择 KMP,往往是在保留平台边界的前提下压缩重复业务代码。

共享到哪一层

KMP 项目里,Android 和 iOS 可以继续各自维护页面入口、导航、系统组件和平台交互。共享模块不需要知道当前页面是 Compose、XML 还是 SwiftUI,它只提供 UseCaseRepository、序列化模型和状态。

这和许多移动端项目原来的分层比较接近。Android 已经有 datadomainfeature 模块时,迁移的重点通常是把纯 Kotlin 的部分移到 commonMain,而不是把整个 UI 重写一遍。

下面是一个常见的登录状态用法。SessionRepository 在共享模块里,Android 的 ViewModel 和 iOS 的 ObservableObject 都可以调用它:

bash 复制代码
// shared/src/commonMain/kotlin/session/SessionRepository.kt
class SessionRepository(
    private val api: AccountApi,
    private val tokenStore: TokenStore,
) {
    suspend fun signIn(email: String, password: String): Session {
        val session = api.signIn(SignInRequest(email, password))
        tokenStore.save(session.accessToken)
        return session
    }
}

AccountApiTokenStore 的接口也放在 commonMain。Android 可用 Ktor + SharedPreferences 或 DataStore 的实现,iOS 用 Ktor Darwin 引擎和 Keychain 实现。业务调用方拿到的都是同一个 Session,不用维护两份接口字段和错误映射。

Android 项目已经用 Kotlin 写业务时,这种迁移的改动比较集中:把无平台依赖的代码挪到共享模块,为存储、推送 token、文件路径这类能力留接口,再在 androidMainiosMain 提供实现。

bash 复制代码
// shared/build.gradle.kts
kotlin {
    androidTarget()
    iosArm64()
    iosSimulatorArm64()

    sourceSets {
        commonMain.dependencies {
            implementation(libs.ktor.client.core)
            implementation(libs.kotlinx.serialization.json)
        }
    }
}

这里没有把 Android 的 ContextViewModel 或 Compose API 放进共享层。它们仍然属于 Android;同样,SwiftUI 和 Keychain 也仍然属于 iOS。共享模块只依赖跨平台库和自己定义的抽象,编译边界会更清楚。

UI 仍在平台侧

React Native 的优势是界面也能共享。页面以 JS/TS 组件为核心,Android 和 iOS 有一套主要的渲染和交互代码。表单、列表、业务页面较多且两端视觉一致时,这能明显降低重复工作。

但对原生团队来说,UI 统一也会改变原来的职责划分。Android 的 Compose 组件、iOS 的 SwiftUI 组件不再是默认入口;遇到复杂动画、系统页面、Widget、通知扩展或平台 SDK 时,需要从 JS 侧调用原生模块,再维护桥接层和两端实现。

KMP 的取舍正好相反。它接受两份 UI 的成本,把共享范围收在逻辑和数据层。设计稿在两端存在明显差异、产品依赖系统交互、或者 Android/iOS 已有成熟代码时,原生 UI 可以少一层适配。

这也不代表 KMP 没有 UI 方案。Compose Multiplatform 已经可以覆盖一部分多端界面,但是否共享 UI 是单独的决策。项目先共享 domaindata 时,不必同时改动所有页面。

代码所有权

架构选择还会影响团队怎么处理需求。React Native 通常需要一支能够维护 JS/TS 页面和原生模块的团队;平台侧需求常常经过共享层,再落到 Android/iOS 实现。

KMP 更适合让平台工程师继续拥有自己的 UI 和系统集成。共享模块由熟悉 Kotlin 的开发者维护,iOS 工程师仍能按 Swift/SwiftUI 的方式处理界面。接口改动集中在共享模型与 API 契约,review 时也更容易看出一次改动影响的是 Android、iOS,还是两端。

一个可落地的目录可以保持得很朴素:

bash 复制代码
shared/
  src/commonMain/  → model、network、repository、use case
  src/androidMain/ → Android 存储、网络引擎实现
  src/iosMain/     → Keychain、Darwin 网络引擎实现

androidApp/        → Compose / ViewModel / Android 系统能力
iosApp/            → SwiftUI / iOS 系统能力

共享代码不应该反向依赖页面层。比如订单提交后需要弹 Toast,commonMain 返回 Result 或领域错误;Android ViewModel 和 iOS 页面各自决定提示文案与展示方式。这样不会为了共享一个提示框,把平台 UI API 引进核心模块。

迁移成本

KMP 的门槛不在把 Kotlin 文件搬到 commonMain,而在边界是否干净。代码里直接读 Build.VERSION、传 Context、依赖 AndroidX 生命周期组件的地方,不能原样复用。先把它们移到 Android 入口或接口实现中,编译错误会告诉你哪些依赖还没有拆开。

iOS 也需要付出工程成本。Kotlin/Native 产物要接进 Xcode,协程、Flow 的暴露方式要让 Swift 调用端容易消费,网络错误与线程切换要在真实设备上确认。共享业务逻辑不等于 iOS 工程不需要维护。

React Native 同样有自己的成本:升级 React Native 版本、处理原生依赖兼容性、调试 JS 和原生之间的问题。两套方案都不是"写一次,后面完全不用管平台"的工具,差别只在复杂度集中在哪一层。

对于一个已有 Android/iOS 客户端、UI 差异又比较多的产品,把账号、商品、订单、搜索、缓存等纯业务代码共享出来,通常比一次性重写所有页面更稳。若团队目标是高度统一的跨端 UI,并且具备长期维护 JS/TS 与原生模块的能力,React Native 的收益会更直接。

最后

你们当前跨平台用的是什么呢?KMP?RN?还是Flutter呢?

#Kotlin #KotlinMultiplatform #ReactNative #Android

相关推荐
windliang1 小时前
Claude Code 源码分析(四):Tool 工具系统-从 Schema 到真实执行
前端·ai编程·claude
10WTW012 小时前
Sprite-Motion-Lab:雪碧图动画编辑器
前端·css·雪碧图·spritemotionlab·10wtw01
夏天要喝冰可乐2 小时前
用 Gitee Go 搭建WorkBuddy云端定时任务
前端·ai编程
天天鸭2 小时前
5 万处中文的老项目实现国际化,如何用架构思维完成改造?
前端·javascript·架构
zbmwa2 小时前
jetpack compose 副作用 produceState
android
程序员黑豆3 小时前
鸿蒙应用开发之持久化存储解析:PersistentStorage / PersistenceV2 / preferences 选型与实战
前端·harmonyos
明月_清风3 小时前
🚀 OpenAI 数据代理架构全解析:从 600 PB 到自然语言的六层上下文工程
前端·后端·架构
明月_清风3 小时前
🚀 从 Foundry 到 AIP:Palantir 发生了什么变化?一篇文章全搞懂
前端·后端
西门啐血3 小时前
Vue 缓存之坑,变量赋值方式和响应式数据
前端·vue.js·缓存