不知道你们在做跨平台混合开发即原生Android+原生iOS+跨平台lib时,是如何选择的。Kotlin Multiplatform 和 React Native 都能减少重复代码,但它们把共享层放在了完全不同的位置。
前者通常保留两端原生 UI,把领域逻辑、数据访问和网络层放进 commonMain;后者把大部分界面和交互交给 JavaScript/TypeScript,再通过原生模块接入设备能力。选择 KMP,往往是在保留平台边界的前提下压缩重复业务代码。

共享到哪一层
KMP 项目里,Android 和 iOS 可以继续各自维护页面入口、导航、系统组件和平台交互。共享模块不需要知道当前页面是 Compose、XML 还是 SwiftUI,它只提供 UseCase、Repository、序列化模型和状态。
这和许多移动端项目原来的分层比较接近。Android 已经有 data、domain、feature 模块时,迁移的重点通常是把纯 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
}
}
AccountApi、TokenStore 的接口也放在 commonMain。Android 可用 Ktor + SharedPreferences 或 DataStore 的实现,iOS 用 Ktor Darwin 引擎和 Keychain 实现。业务调用方拿到的都是同一个 Session,不用维护两份接口字段和错误映射。
Android 项目已经用 Kotlin 写业务时,这种迁移的改动比较集中:把无平台依赖的代码挪到共享模块,为存储、推送 token、文件路径这类能力留接口,再在 androidMain、iosMain 提供实现。
bash
// shared/build.gradle.kts
kotlin {
androidTarget()
iosArm64()
iosSimulatorArm64()
sourceSets {
commonMain.dependencies {
implementation(libs.ktor.client.core)
implementation(libs.kotlinx.serialization.json)
}
}
}
这里没有把 Android 的 Context、ViewModel 或 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 是单独的决策。项目先共享 domain 和 data 时,不必同时改动所有页面。
代码所有权
架构选择还会影响团队怎么处理需求。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呢?