第1章 Compose 状态管理基础:State、remember、mutableStateOf
状态管理,说白了就是让 UI 能“记住”数据,再根据数据变化自动刷新界面。刚开始接触 Compose 时,我总觉得它跟传统的 View 系统完全是两回事——没有 findViewById,没有 setText,界面全是函数调出来的。那数据怎么存?怎么改?改完了怎么通知界面重绘?
这背后的核心,其实就三个东西:State、remember、mutableStateOf。把这三个概念搞明白,Compose 的状态管理就算入门了。
1.1 什么是 State?为什么它很重要?
State 是 Compose 世界里最基础的概念。你可以把它理解成一个“可观察的值”——当这个值发生变化时,Compose 会自动通知所有依赖它的 UI 去重新组合(也就是重组)。
举个例子,一个计数器:
@Composable
fun Counter() {
val count = remember { mutableStateOf(0) }
Button(onClick = { count.value++ }) {
Text("Count: ${count.value}")
}
}
这里 count.value 就是一个 State。每次点击按钮,值加 1,Compose 检测到 State 变了,就只重新执行 Text 这一部分,其他没变的地方不动。这种精准的局部更新,就是 State 的价值所在。
我个人习惯把 State 比喻成“感应器”——它装在数据上,数据一抖,它就给 Compose 发信号:“喂,这边要刷新了!”
mutableStateOf。
1.2 remember:让数据活过重组
Compose 的函数会反复执行——每次重组都会重新调用你的 Composable 函数。那问题来了:如果每次重组都重新 val count = mutableStateOf(0),那岂不是每次都创建一个新的 State?之前存的值不就丢了?
这就是 remember 登场的原因。
它的作用就是在重组期间“记住”计算结果。只要 key 没变(不传 key 默认记住一次),remember 就会把上一次的值缓存下来,直接返回给你,不再重新计算。
@Composable
fun MyScreen() {
// 每次重组都不会重新创建 State,而是复用上一次的
val state = remember { mutableStateOf("Hello") }
Text(state.value)
}
我在项目中遇到过一个问题:忘了加 remember,结果每次 UI 刷新都重新创建了一个新的 State,所有输入框内容都被清空了。找 bug 找了半天才发现少写了 remember。嗯,这个坑踩过一次就不会忘了。
remember 只能帮你在当前 Composable 的生命周期内保持数据。如果你的 Composable 因为配置变化或 Activity 重建而被销毁,remember 的数据也会丢失。那就要用 rememberSaveable 了(后面章节会讲)。
1.3 mutableStateOf:创建可变状态的真身
mutableStateOf 是 Compose 提供的一个函数,它会返回一个 MutableState 对象。这个对象有一个 .value 属性,读它的时候,Compose 会记住当前读取的上下文;写它的时候,Compose 就会触发重组。
对比一下三个东西的职责,用表格看得更清楚:
| 概念 | 角色 | 关键特性 |
|---|---|---|
| State | 接口 / 契约 | 定义“可观察的值”,只读 |
| remember | 缓存机制 | 跨越重组保持对象实例 |
| mutableStateOf | 工厂函数 | 创建可读写的 MutableState 实例 |
三者的关系是这样的:State 是约定,mutableStateOf 是实现,remember 是保障。没有 remember,mutableStateOf 每次重组都会重新生成,数据就丢了;没有 mutableStateOf,State 只是个空接口;没有 State 这个类型系统,Compose 没法识别哪些值需要被观察。
1.4 组合使用:一个完整的例子
来写个登录表单的例子。这个例子虽然简单,但能直观感受三个概念如何协作:
@Composable
fun LoginForm() {
// 用 remember + mutableStateOf 创建可观察状态
val username = remember { mutableStateOf("") }
val password = remember { mutableStateOf("") }
Column(modifier = Modifier.padding(16.dp)) {
TextField(
value = username.value,
onValueChange = { username.value = it },
label = { Text("用户名") }
)
Spacer(modifier = Modifier.height(8.dp))
TextField(
value = password.value,
onValueChange = { password.value = it },
label = { Text("密码") },
visualTransformation = PasswordVisualTransformation()
)
Button(
onClick = {
// 这里可以读取状态值做登录逻辑
println("尝试登录:$username.value / $password.value")
},
modifier = Modifier.padding(top = 16.dp)
) {
Text("登录")
}
}
}
每次用户在 TextField 里输入,onValueChange 会更新对应的 mutableStateOf 的 .value,Compose 检测到状态变化,自动重组 UI。而 remember 确保 username 和 password 这两个 State 对象不会因为重组被丢弃。
我曾经被问过一个面试题:“如果去掉 remember,TextField 还能正常工作吗?” 答案是:能工作,但每次输入都会让整个 Composable 重组,而且 mutableStateOf 每次都被重建,输入框里的文字会立刻消失——因为每次都是一个全新的 State 实例,旧的值没了。所以 remember 加不加,差别巨大。
1.5 知识结构图:State 管理核心流程
下面这张图帮你理清三者的关系,以及 Compose 重组是如何被触发的:
这个流程其实很简单:Composable 函数调用 remember 包裹 mutableStateOf,得到一个 State 对象。这个对象暴露 .value 供读取和写入。当写入新值时,Compose 自动把更新“推送”给所有读取过该 State 的 UI 部分,触发重组。
1.6 我来总结一下本章的实用要点
- 每次需要可变状态时,默认组合:
remember { mutableStateOf(初始值) }。这是最常用的写法。 - State 是接口,mutableStateOf 是工厂,remember 是守卫。 三者缺一不可(除非你用其他方式保持状态,比如 ViewModel)。
- 别忘了给
mutableStateOf一个合理的初始值。 如果初始值本身就是空,那要小心可能引发的空指针,尤其在复杂对象时。 - 不要在非 Composable 作用域里直接调用
mutableStateOf(除非你很清楚自己在做啥)。它需要 Composable 的观察上下文才能正确触发重组。
remember { mutableStateOf(...) }。记住 remember 保实例,mutableStateOf 造状态,State 做观察。