8、状态管理(上):remember与mutableStateOf、State与MutableState、状态提升、ViewModel集成
状态管理,说白了就是解决「数据变了,UI怎么跟着变」这个问题。
我刚接触 Compose 时,第一反应是:这不就是 setState 吗?后来发现,Compose 的状态管理比我想象的深得多。它不只是一个 API,而是一套完整的响应式编程模型。
这一章,我们先啃下状态管理的核心基础。后面还有下篇,会讲更高级的玩法。
8.1 从 mutableStateOf 说起
在 Compose 里,最简单的状态声明方式就是 mutableStateOf。
@Composable
fun Counter() {
val count = remember { mutableStateOf(0) }
Button(onClick = { count.value++ }) {
Text("点击了 ${count.value} 次")
}
}
这段代码里,mutableStateOf(0) 创建了一个可观察的状态容器。remember 保证重组时不会重新创建。
我刚开始写的时候犯过一个低级错误:忘了加 remember。结果每次重组都重置为 0,按钮怎么点都不变。嗯,这个坑我替你们踩过了。
核心要点:
mutableStateOf返回的是MutableState<T>类型- 读取用
.value,修改也是.value = xxx - 必须配合
remember使用,否则状态会丢失
8.2 State 与 MutableState 的区别
这两个接口,很多人搞混过。我简单说一下:
| 接口 | 可读 | 可写 | 典型来源 |
|---|---|---|---|
State<T> |
✅ | ❌ | 派生状态、ViewModel 暴露 |
MutableState<T> |
✅ | ✅ | 局部状态、表单输入 |
State 是只读的,MutableState 继承自 State,多了写的能力。
你想想看,为什么要有这个区分?
因为在实际项目中,我们希望「数据源」只有一个。ViewModel 里持有 MutableState,但对外只暴露 State。这样 UI 层只能读,不能乱改。我在项目里见过有人直接把 MutableState 传到各个 Composable 里,结果到处都在改数据,调试起来简直噩梦。
我的习惯:ViewModel 内部用 MutableState,对外暴露用 State。UI 层只读,不改。
8.3 remember 的底层逻辑
remember 的作用是「记住」一个值,跨重组保留。
@Composable
fun RememberDemo() {
val random = remember { Random.nextInt() }
val random2 = Random.nextInt()
Text("remember: $random") // 重组时不变
Text("no remember: $random2") // 重组时重新计算
}
但 remember 不是万能的。它只在当前 Composable 的生命周期内有效。如果 Composable 被移出组合树,remember 的值就丢了。
我曾经遇到一个场景:用户切换 Tab 时,之前输入的内容全没了。原因就是 Tab 切换导致 Composable 被销毁重建,remember 自然保不住数据。这时候就需要 ViewModel 出马了。
注意:remember 不跨配置变更(如屏幕旋转)。如果需要持久化,请用 ViewModel 或 SavedStateHandle。
8.4 状态提升:把状态往上提
状态提升,是 Compose 里一个非常重要的设计模式。说白了就是:把状态放到父组件里,子组件只负责触发事件。
@Composable
fun Parent() {
var text by remember { mutableStateOf("") }
Child(
value = text,
onValueChange = { text = it }
)
}
@Composable
fun Child(value: String, onValueChange: (String) -> Unit) {
TextField(value = value, onValueChange = onValueChange)
}
这样做的好处很明显:
- 单一数据源:状态只在一个地方维护
- 可复用:
Child不依赖任何外部状态,传入什么就显示什么 - 可测试:直接传参数就能测试子组件
我个人习惯:如果一个 Composable 内部状态超过两个,或者状态需要被兄弟组件共享,就立刻做状态提升。
8.5 ViewModel 集成:真正的状态管理
前面说的 remember 和状态提升,适合简单场景。但真实项目里,状态往往需要跨页面、跨配置变更存活。这时候就要上 ViewModel 了。
class MyViewModel : ViewModel() {
private val _count = mutableStateOf(0)
val count: State<Int> = _count
fun increment() {
_count.value++
}
}
@Composable
fun MyScreen(viewModel: MyViewModel = viewModel()) {
val count by viewModel.count
Button(onClick = { viewModel.increment() }) {
Text("点击了 $count 次")
}
}
注意这里:viewModel() 是 Compose 提供的函数,它会自动管理 ViewModel 的生命周期。屏幕旋转时,ViewModel 不会销毁,状态自然保留。
ViewModel 的优势:
- 跨配置变更存活
- 业务逻辑与 UI 分离
- 天然支持协程作用域
- 方便单元测试
我在项目中遇到过一个问题:ViewModel 里用 mutableStateOf 时,忘了用 by 委托,结果每次都要写 .value。虽然不影响功能,但代码看起来啰嗦。后来我统一用 by 委托,清爽多了。
8.6 本章知识体系
下面这张图,把本章的核心概念串起来了:
8.7 避坑指南
最后,分享几个我踩过的坑:
- 不要在 Composable 里直接创建 ViewModel:用
viewModel()函数,不要自己 new。 - 不要用 var 声明状态:
var count by remember { mutableStateOf(0) }才是正确写法。 - 状态提升别过度:如果状态只在当前组件内使用,没必要提到父组件去。
- ViewModel 里别持有 Composable 引用:ViewModel 生命周期比 Composable 长,容易内存泄漏。
嗯,这一章的内容就到这里。状态管理是 Compose 的核心,理解了这些基础,后面学高级特性会轻松很多。