Compose 状态
本教程共 100 篇 · 第 46 篇 · 更新于 2026-07-28 · 约 9 分钟阅读
46. Compose 状态
本节目标:理解 Compose 中状态的概念,学会用 remember 和 mutableStateOf 管理状态,掌握状态提升原则和单向数据流,写出可交互的界面。
什么是状态
状态就是「会随时间变化的值」。用户在输入框里打字,文字就是状态;勾选了复选框,勾选状态就是状态;从网络加载了数据,数据就是状态。
在命令式 UI 里,状态存在控件内部(EditText.text),你手动读写。在 Compose 声明式 UI 里,状态和界面分离:数据变了,界面自动更新。但前提是—你得让 Compose 知道数据变了。
remember 记住值
可组合函数每次重组都会重新执行,函数里的局部变量会被重新初始化。要保留值,用 remember:
@Composable
fun Counter() {
// remember 让 count 在重组时保留
val count = remember { 0 }
Button(onClick = { /* 怎么改? */ }) {
Text("点了 $count 次")
}
}
但这里有个问题:count 是 val,改不了。remember 只能记住值,不能让值可变。
mutableStateOf 可观察状态
mutableStateOf 创建一个可观察的状态容器。当它的值变化时,Compose 自动安排重组:
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
Button(onClick = { count++ }) {
Text("点了 $count 次")
}
}
这里有三个关键点:
mutableStateOf(0)创建可观察状态,初始值 0。remember { }把这个状态对象在重组间保留。by委托语法让你直接读写count,不用写.value。
Note三种等价写法,选你喜欢的:
val count = remember { mutableStateOf(0) } // 用 count.value 读写 var count by remember { mutableStateOf(0) } // 用 count 直接读写(推荐) val (count, setCount) = remember { mutableStateOf(0) } // 解构
by 委托需要导入:
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
状态驱动的界面
Compose 的核心模式是:状态变化 -> 重组 -> 界面更新。你永远不直接改界面,而是改状态,让界面跟着状态走。
@Composable
fun TodoApp() {
var text by remember { mutableStateOf("") }
val items = remember { mutableStateListOf<String>() }
Column {
Row {
TextField(
value = text,
onValueChange = { text = it },
modifier = Modifier.weight(1f)
)
Button(onClick = {
if (text.isNotBlank()) {
items.add(text)
text = ""
}
}) {
Text("添加")
}
}
items.forEach { item ->
Text(item, modifier = Modifier.padding(8.dp))
}
}
}
mutableStateListOf 创建可观察的列表,增删时界面自动更新。
状态提升(State Hoisting)
状态写在哪很重要。如果一个可组合函数内部自己管理状态,它就是「有状态的」;如果状态由外部传入,它就是「无状态的」。
把状态从组件内部提到外部,叫状态提升。提升后的组件更通用、更可测试、更易复用。
有状态版本
@Composable
fun StatefulCounter() {
var count by remember { mutableStateOf(0) } // 状态在里面
Button(onClick = { count++ }) {
Text("点了 $count 次")
}
}
无状态版本
@Composable
fun StatelessCounter(
count: Int, // 状态从外面传进来
onIncrement: () -> Unit // 事件交给外面处理
) {
Button(onClick = onIncrement) {
Text("点了 $count 次")
}
}
调用方负责状态:
@Composable
fun CounterScreen() {
var count by remember { mutableStateOf(0) }
StatelessCounter(
count = count,
onIncrement = { count++ }
)
}
Tip
- 状态提升的原则:状态向下传递,事件向上传递。数据由父组件持有,子组件只负责展示和发出事件,不自己改状态。这叫单向数据流。
单向数据流
Compose 推荐的架构是单向数据流(Unidirectional Data Flow):
状态(State) -> 界面(UI) -> 事件(Event) -> 状态更新
- 状态决定界面显示什么
- 用户操作产生事件
- 事件更新状态
- 状态变化触发重组
@Composable
fun SearchScreen() {
var query by remember { mutableStateOf("") }
val results = remember(query) {
if (query.isBlank()) emptyList()
else allItems.filter { it.contains(query, ignoreCase = true) }
}
Column {
SearchBar(
query = query,
onQueryChange = { query = it } // 事件向上传
)
ResultsList(results) // 状态向下传
}
}
Note
remember(query) { }带参数的版本:当query变化时才重新计算results,不变时直接用缓存值。这避免每次重组都重新过滤列表。
rememberSaveable 保存状态
remember 在配置变更(旋转屏幕)时会丢失,因为 Activity 重建了。用 rememberSaveable 替代,它会把状态保存到 Bundle:
var count by rememberSaveable { mutableStateOf(0) }
旋转屏幕后 count 还在。它支持基本类型和实现了 Saver 的对象。
// 保存自定义对象
data class User(val name: String, val age: Int)
var user by rememberSaveable {
mutableStateOf(User("小明", 20))
}
Warning
- 不是所有对象都能自动保存。复杂对象需要实现自定义
Saver,或用@Parcelize注解让数据类支持 Parcelable。rememberSaveable底层走的是onSaveInstanceState。
状态存储在哪
| 状态位置 | 适用场景 | 生命周期 |
|---|---|---|
组件内 remember | 简单 UI 状态 | 组件存在期间 |
rememberSaveable | 需要挺过配置变更 | Activity 生命周期 |
ViewModel | 业务数据 | 跨配置变更存活(第 78 章讲) |
CompositionLocal | 主题、语言等全局值 | 组合树生命周期 |
Tip
- 经验法则:简单界面状态用
remember,要挺过旋转用rememberSaveable,业务数据放ViewModel。别把所有状态都塞 ViewModel,简单的 UI 状态在组件里管理就好。
小结
状态是 Compose 的驱动力:状态变 -> 重组 -> 界面更新。remember + mutableStateOf 管理可观察状态,rememberSaveable 让状态挺过配置变更。状态提升让组件更可复用:状态向下传,事件向上传,形成单向数据流。下一节学习文本和输入组件。