14、Compose KTX:Compose与KTX的结合,remember与mutableStateOf的扩展

Compose 出来之后,很多人第一反应是:「这不就是 Android 版的 SwiftUI 吗?」
嗯,表面看确实像。但真正用起来你会发现,Compose 的底层逻辑和 View 体系完全不同。
而 KTX 在 Compose 里扮演的角色,说白了就是帮我们把那些「本该由框架做、但框架没做」的琐事给补上。

我个人习惯把 Compose KTX 理解为「胶水层」。它不改变 Compose 的核心理念,而是让 Kotlin 的语法糖和 Compose 的状态管理结合得更自然。
今天我们就来聊聊 remembermutableStateOf 这两个最常用的 API,看看 KTX 是怎么给它们「加 buff」的。

14.1 remember:不只是缓存

remember 是 Compose 里最基础的记忆机制。
它的作用是在重组时保留值,避免重复计算。
但如果你只把它当缓存用,那就太小看它了。

我在项目中遇到过一个问题:某个列表项需要根据网络状态显示不同图标。
一开始我直接在 Composable 里写 if (isOnline) ... else ...,结果每次重组都重新创建图标对象。
后来改成 remember(isOnline) { if (isOnline) IconA else IconB },性能明显提升。

核心要点:

  • remember 的 key 参数决定了何时重新计算
  • 不传 key 时,只在首次进入组合时计算
  • 传了 key,key 变化时才会重新计算

你想想看,如果 key 传的是一个频繁变化的值,那 remember 就退化成普通变量了。
所以选 key 要谨慎,别把整个 ViewModel 传进去。

14.2 mutableStateOf:可观察的容器

mutableStateOf 是 Compose 状态管理的基石。
它包装的值一旦改变,就会触发读取它的 Composable 重组。

但有个细节很多人忽略:mutableStateOf 默认是线程安全的吗?
答案是否定的。它只在 Compose 的调度器下保证一致性。
如果你在后台线程修改它,可能会引发奇怪的问题。

我曾经踩过的坑:

在协程的 Dispatchers.IO 里直接修改 mutableStateOf 的值,结果 UI 没有更新。
后来发现需要切回 Dispatchers.Main 或者用 snapshotFlow 做桥接。

所以我的建议是:
所有状态修改都放在 Compose 的上下文中,或者通过 LaunchedEffect 来驱动。

14.3 KTX 带来的扩展

Compose KTX 提供了一些非常实用的扩展函数,让 remembermutableStateOf 用起来更顺手。

扩展函数 作用 示例
rememberSaveable 在配置变更时保存状态 rememberSaveable { mutableStateOf(0) }
derivedStateOf 根据其他状态派生新状态 val isEven by remember { derivedStateOf { count % 2 == 0 } }
mutableStateListOf 可观察的列表 val list = remember { mutableStateListOf() }

我个人最常用的是 rememberSaveable
以前用 ViewModel 保存状态,后来发现很多简单场景根本不需要 ViewModel。
一个 rememberSaveable 就能搞定屏幕旋转后的状态恢复。

小技巧:

rememberSaveable 默认使用 Bundle 存储,所以只能保存基本类型和 Parcelable。
如果需要保存复杂对象,可以自定义 Saver

14.4 实战:一个计数器组件

光说不练假把式。我们写一个带历史记录的计数器,看看这些 API 怎么配合。

@Composable
fun CounterWithHistory() {
    val count = rememberSaveable { mutableStateOf(0) }
    val history = remember { mutableStateListOf<Int>() }
    val isEven by remember {
        derivedStateOf { count.value % 2 == 0 }
    }

    Column(modifier = Modifier.padding(16.dp)) {
        Text("当前值: ${count.value}")
        Text("是否为偶数: $isEven")

        Button(onClick = {
            count.value++
            history.add(count.value)
        }) {
            Text("加一")
        }

        Button(onClick = {
            if (history.isNotEmpty()) {
                history.removeLast()
                count.value = history.lastOrNull() ?: 0
            }
        }) {
            Text("撤销")
        }

        Text("历史记录:")
        history.forEach { value ->
            Text("- $value")
        }
    }
}

这段代码里:

  • rememberSaveable 保证旋转屏幕后 count 不丢失
  • mutableStateListOf 让列表变化也能触发重组
  • derivedStateOf 自动计算 isEven,避免手动同步

嗯,这里要注意:mutableStateListOf 的修改操作(add/remove)会自动通知 Compose。
但如果你直接替换整个列表,比如 history = newList,那就不会触发重组了。
这是 Compose 的「引用不变」原则,和 React 的不可变数据有点像。

14.5 知识体系图

下面这张图展示了 Compose KTX 中状态管理的核心关系:

Compose 状态管理 remember 缓存计算结果 key 控制重组 mutableStateOf 可观察容器 触发重组 KTX 扩展 rememberSaveable derivedStateOf mutableStateListOf

从图中可以看出,Compose 的状态管理以 remembermutableStateOf 为根基,KTX 扩展在此基础上提供了更便捷的 API。
三者配合,基本能覆盖 90% 的 UI 状态场景。

14.6 避坑指南

最后分享几个我踩过的坑:

  • 不要在 remember 里做耗时操作:remember 是在组合阶段执行的,如果里面做了网络请求或数据库查询,会卡 UI 线程。
  • mutableStateOf 不要和 LiveData 混用:两者虽然都是可观察的,但生命周期不同。混用容易导致内存泄漏或状态不同步。
  • rememberSaveable 不是万能的:它只能保存简单数据。如果保存大对象,建议用 ViewModel + SavedStateHandle。

总结一下:

Compose KTX 让状态管理变得更 Kotlin 化。
remember 负责记忆,mutableStateOf 负责通知,KTX 扩展负责让两者更好用。
掌握这三者的关系,你就能写出既高效又优雅的 Compose 代码。


公众号:蓝海资料掘金营,微信deep3321