状态管理与ViewModel

2026-03-31 21:29 状态管理与ViewModel已关闭评论

003 状态管理与 ViewModel:我的Compose实战心得

1. 从「改 View」到「改状态」的思维转变

刚开始接触 Compose 时,我总是不自觉地想用 XML 时代的老方法:editText.setText(s)button.isEnabled = flag,总想着直接操作 UI 组件。但 Compose 彻底改变了这个逻辑——它让我明白,UI 应该是状态的函数。

Compose 的核心逻辑是:Composable 读取状态 → 状态变化 → 相关 Composable 自动重组。所以我的工作重心,从直接操作 View 变成了如何管理好数据状态,并遵循 单向数据流 的原则(事件向上传递,状态向下流动)。这个思维转变,是我上手 Compose 后最大的收获之一。

2. 我的入门搭档:remember + mutableStateOf

对于界面局部、只在当前重组范围内需要保持的状态(比如一个控件的展开/收起状态、输入框的草稿内容),我通常会用 remembermutableStateOf 这对组合拳。

import androidx.compose.runtime.*

@Composable
fun Counter() {
    var count by remember { mutableIntStateOf(0) }
    Column(modifier = Modifier.padding(16.dp)) {
        Text("次数: $count")
        Button(onClick = { count++ }) { Text("+1") }
    }
}

这里有几个关键点是我在实践中总结的:
- remember 是关键:它保证在重组后,状态实例能被保留下来。如果没有它,每次重组 count 都会重新被初始化为 0,状态就丢了。
- by 委托:这可不是语法糖那么简单。它让 count 的读写都通过 MutableStatevalue 属性进行,而赋值操作会自动触发重组,非常方便。

另外一个小贴士:如果你的项目 Kotlin 版本在 1.9+,推荐使用 mutableIntStateOf 这类具体类型的方法,可以避免不必要的装箱开销。老项目用 mutableStateOf(0) 也完全没问题。

3. 状态提升(State Hoisting):让组件更纯粹

随着组件变复杂,我很快发现把状态都写在组件内部会让测试和复用变得困难。这时就需要 状态提升——把状态提升到调用方,让子组件变成**无状态(stateless)**的纯展示组件。这其实和 MVVM 中 View 只负责展示和发送事件 的思想不谋而合。

@Composable
fun SearchBar(
    query: String,
    onQueryChange: (String) -> Unit,
    onSearch: () -> Unit
) {
    Row(verticalAlignment = Alignment.CenterVertically) {
        OutlinedTextField(
            value = query,
            onValueChange = onQueryChange,
            modifier = Modifier.weight(1f),
            label = { Text("关键词") }
        )
        Button(onClick = onSearch, modifier = Modifier.padding(start = 8.dp)) {
            Text("搜索")
        }
    }
}

@Composable
fun SearchScreen() {
    var query by remember { mutableStateOf("") }
    SearchBar(
        query = query,
        onQueryChange = { query = it },
        onSearch = { /* 用 query 调接口 */ }
    )
}

这样做之后,SearchBar 变得非常“纯粹”,我可以轻松地给它不同的初始值,或者在不同的场景复用,预览也变得简单。

4. ViewModel + Compose:业务状态的归宿

对于业务状态,或者需要跨配置变更(比如旋转屏幕)保持的状态remember 就不够用了。这时候就该 ViewModel 出场了——它和我们在 Fragment/Activity 中使用的方式一脉相承。

依赖配置(以当前版本为例)

implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.7")
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.7")

这里注意,lifecycle-runtime-compose 提供了 collectAsState() 等扩展,让我们能在 Composable 中方便地收集 StateFlow

在 Composable 中获取 ViewModel

import androidx.lifecycle.viewmodel.compose.viewModel

@Composable
fun UserScreen(vm: UserViewModel = viewModel()) {
    val uiState by vm.uiState.collectAsState()
    // 根据 uiState 渲染;事件调用 vm.onXxx()
}

在 ViewModel 内部,我习惯用 StateFlow / MutableStateFlow 来暴露 UI 状态,感觉比 LiveData 更符合 Kotlin 协程的生态:

data class UserUiState(val name: String = "", val loading: Boolean = false)

class UserViewModel : ViewModel() {
    private val _uiState = MutableStateFlow(UserUiState())
    val uiState: StateFlow<UserUiState> = _uiState.asStateFlow()

    fun load() {
        viewModelScope.launch {
            _uiState.update { it.copy(loading = true) }
            // ... 模拟网络请求
            _uiState.update { it.copy(loading = false, name = "Alice") }
        }
    }
}

在 Composable 中收集状态就一行代码:val state by vm.uiState.collectAsState()。记得确保相关依赖版本对齐。

5. 与 LiveData 的衔接(给迁移中的项目)

如果你的老项目还在大量使用 LiveData,也不用急着全部重写。可以用 observeAsState()(来自 androidx.compose.runtime:runtime-livedata)来桥接,逐步迁移到 StateFlow

6. 我踩过的坑:反模式提示

  • 在 Composable 里直接 val list = mutableListOf() 且不用 remember:重组时数据会丢失,或者列表被重复初始化,导致奇怪的问题。
  • 把非 UI 的副作用(网络请求、读写数据库)直接写在 Composable 函数体里:这是大忌!应该放到 LaunchedEffect 或者 ViewModel 中处理(这部分我们会在第 008 篇详细讲)。

7. 动手练习

光说不练假把式,我建议你动手试试:

  1. 尝试把「登录页」拆成:无状态的 LoginForm(email, password, onEmailChange, ...),然后在父级用 remember 持有输入状态。
  2. ViewModel 持有一个包含 loadingerrorMessage 的状态,模拟按钮点击触发“假请求”,并在界面上正确显示加载状态和错误信息。

实践出真知,写一遍印象会深刻很多。

下一篇,我们来聊聊列表:004-列表LazyColumn与RecyclerView对照.md

当前文章价值6.13元,扫一扫支付后添加微信提供帮助!(如不能解决您的问题,可以申请退款)

你可能感兴趣的文章

来源:每日教程每日一例,深入学习实用技术教程,关注公众号TeachCourse
转载请注明出处: https://teachcourse.cn/3832.html ,谢谢支持!

资源分享

Building and Running Overview Building and Running Overvi
新版本ADT创建Android项目无法自动生成R文件解决办法 新版本ADT创建Android项目无
Python库Flask和SQLite数据库创建简单CRUD(创建、读取、更新、删除)应用的示例 Python库Flask和SQLite数据
ViewPager+FragmentPagerAdapter实现简单新闻客户端 ViewPager+FragmentPagerAd

评论已关闭!