第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 发信号:“喂,这边要刷新了!”

💡 小提醒:State 本身只是接口,你没法直接 new 一个 State 出来。真正创建 State 实例,靠的是 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 重组是如何被触发的:

Compose 状态管理核心流程 Composable 函数 remember { ... } mutableStateOf(初始值) State 对象 (可观察) 读取 .value 触发重组 关键点 • 每个重组不会重新创建 State • 写 .value 触发重组 • remember 保证跨重组缓存 • mutableStateOf 创建可变状态

这个流程其实很简单:Composable 函数调用 remember 包裹 mutableStateOf,得到一个 State 对象。这个对象暴露 .value 供读取和写入。当写入新值时,Compose 自动把更新“推送”给所有读取过该 State 的 UI 部分,触发重组。

1.6 我来总结一下本章的实用要点

  • 每次需要可变状态时,默认组合:remember { mutableStateOf(初始值) }。这是最常用的写法。
  • State 是接口,mutableStateOf 是工厂,remember 是守卫。 三者缺一不可(除非你用其他方式保持状态,比如 ViewModel)。
  • 别忘了给 mutableStateOf 一个合理的初始值。 如果初始值本身就是空,那要小心可能引发的空指针,尤其在复杂对象时。
  • 不要在非 Composable 作用域里直接调用 mutableStateOf(除非你很清楚自己在做啥)。它需要 Composable 的观察上下文才能正确触发重组。
📌 一句话总结:想让 UI 跟随数据自动更新,就用 remember { mutableStateOf(...) }。记住 remember 保实例,mutableStateOf 造状态,State 做观察。