一、状态管理:从入门到通透

状态管理,说白了就是解决「数据变了,UI怎么跟着变」的问题。我刚开始用 Jetpack Compose 时,最不适应的就是这点——以前写 XML 布局,改数据得手动调 findViewById 再 setText,现在呢?数据一变,UI 自动刷新。听起来很美好,但坑也不少。

这一章,我会把 Compose 状态管理的核心概念掰开揉碎讲清楚。你跟着走一遍,基本就能应付 90% 的开发场景了。

1. remember 与 mutableStateOf:最基础的状态容器

先看一个最简单的例子:

@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }

    Button(onClick = { count++ }) {
        Text("点击了 $count 次")
    }
}

这段代码里,mutableStateOf(0) 创建了一个可观察的状态对象,初始值是 0。remember 的作用是让这个对象在重组时不被重新创建。嗯,这里要注意:没有 remember,每次重组都会重新生成一个状态,那之前的数据就全丢了

核心理解:

  • mutableStateOf 创建的是 MutableState<T> 对象
  • by 关键字是 Kotlin 的委托属性语法,等价于 count.value
  • remember 保证状态在重组时存活

我个人习惯用 by 委托,代码更简洁。但如果你团队里有新人,写成 count.value 可能更直观——看个人喜好。

2. State 与 MutableState:读写分离的设计

Compose 把状态分成了两个接口:

接口 能力 使用场景
State<T> 只读,只能获取 .value 对外暴露给其他组件
MutableState<T> 可读可写,能修改 .value 内部管理数据

为什么要分开?我在项目中遇到过一个问题:一个子组件不小心改了父组件的状态,导致整个界面乱跳。后来我强制要求:对外只传 State,内部用 MutableState。这样谁改谁负责,责任清晰。

小技巧:asState() 可以把 MutableState 转成 State 暴露出去,但 Compose 里更常见的做法是直接用 by 委托,然后只暴露 getter。

3. 状态提升:把数据往上提

状态提升是 Compose 里最重要的设计模式之一。说白了就是:谁用数据,谁管理数据;多个组件共享数据,就把数据提到它们的共同父级

举个例子:

@Composable
fun Parent() {
    var text by remember { mutableStateOf("") }

    ChildA(text = text)
    ChildB(onTextChange = { text = it })
}

@Composable
fun ChildA(text: String) {
    Text("当前输入:$text")
}

@Composable
fun ChildB(onTextChange: (String) -> Unit) {
    TextField(value = "", onValueChange = onTextChange)
}

你看,text 状态在 Parent 里管理,ChildA 只读,ChildB 通过回调修改。这就是典型的状态提升——数据往父级提,子组件只负责展示和通知。

我曾经踩过的坑:把状态放在太底层的组件里,结果上层想用的时候拿不到,只能一层层传回调。最后代码又臭又长。记住一个原则:如果一个状态被两个以上不相关的组件使用,就往上提一层

4. 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
    Text("计数:$count")
}

ViewModel 的生命周期比 Composable 长,它会在 Activity 重建时存活。我一般把业务逻辑都放在 ViewModel 里,Composable 只负责 UI 渲染——这样测试也好写,逻辑也好复用。

工程实践建议:

  • UI 状态用 mutableStateOf 放在 ViewModel 里
  • 复杂业务逻辑用 viewModelScope.launch 处理异步
  • Composable 里只调用 viewModel 的方法,不直接操作状态

5. LiveData / Flow 转 State:桥接旧世界

很多项目之前用的是 LiveData 或 Flow,不可能全改成 mutableStateOf。Compose 提供了现成的转换函数:

// LiveData 转 State
val name by viewModel.userName.observeAsState("")

// Flow 转 State
val data by viewModel.dataFlow.collectAsState(initial = null)

我个人更推荐 Flow,因为它在 ViewModel 里更好用——stateIn 操作符可以配合 SharingStarted 控制共享策略。不过如果你团队已经习惯了 LiveData,也没必要强行换。

注意:collectAsState 会在 Composable 进入组合时开始收集,离开时自动取消。不用担心内存泄漏。

知识体系总览

下面这张图总结了本章的核心内容:

Compose 状态管理知识体系 状态管理 remember + mutableStateOf State / MutableState 状态提升 ViewModel 集成 LiveData / Flow 转 State 核心原则:数据向上提,事件向下传 💡 选择建议 简单页面 → remember + mutableStateOf 复杂页面 → ViewModel + mutableStateOf / Flow

本章小结

状态管理是 Compose 的基石。你只要记住几个关键点:

  • remember 保活,mutableStateOf 创建可观察数据
  • State 只读,MutableState 可写——对外暴露时注意封装
  • 状态提升 解决组件间数据共享,数据往父级提
  • ViewModel 解决生命周期问题,适合工程化
  • LiveData/Flow 可以通过扩展函数无缝接入 Compose

这些概念你多写几个页面自然就熟了。下一章我们会深入 Compose 的布局系统,看看那些好用的 Column、Row、Box 到底怎么玩出花来。


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