Compose 管理状态 详解

Jetpack Compose 状态管理详解

Jetpack Compose 是 Android 的现代声明式 UI 工具包,状态管理是其核心概念之一。以下是完整的状态管理指南:

一、状态基础概念

1.1 什么是状态?

状态是随时间变化的数据,会直接影响 UI 的显示。在 Compose 中,状态变化会触发重组(Recomposition)。

kotlin

复制代码
// 传统命令式 vs Compose 声明式
// 传统:手动更新 View
// Compose:状态变化 → 自动重组 UI

1.2 状态与重组的关系

plain

复制代码
状态变化 → 触发重组 → 重新执行 Composable → UI 更新

二、状态类型详解

2.1 基础状态类型

类型 用途 特点

|------------------------|-----------|------------|
| mutableStateOf | 基本类型、对象 | 最常用,自动观察 |
| mutableStateListOf | List 集合 | 支持增删改查自动观察 |
| mutableStateMapOf | Map 集合 | 键值对自动观察 |
| mutableIntStateOf | Int 专用 | 性能优化,避免装箱 |
| mutableLongStateOf | Long 专用 | 性能优化 |
| mutableFloatStateOf | Float 专用 | 性能优化 |
| mutableDoubleStateOf | Double 专用 | 性能优化 |

2.2 代码示例

kotlin

复制代码
// 基础状态
var count by remember { mutableIntStateOf(0) }
var name by remember { mutableStateOf("") }
var isChecked by remember { mutableStateOf(false) }

// 列表状态
val items = remember { mutableStateListOf<String>() }
items.add("新项")  // 自动触发重组

// Map 状态
val userMap = remember { mutableStateMapOf<String, String>() }
userMap["name"] = "张三"  // 自动触发重组

三、remember 家族详解

3.1 remember 变体对比

函数 作用 使用场景

|--------------------------|-------|-----------|
| remember | 普通记忆 | 配置不变时 |
| rememberSaveable | 状态保存 | 配置变化/进程死亡 |
| rememberUpdatedState | 更新状态 | 副作用中使用最新值 |
| rememberCoroutineScope | 协程作用域 | 启动协程 |

3.2 代码示例

kotlin

复制代码
// 1. 普通 remember - 旋转屏幕后丢失
var text by remember { mutableStateOf("") }

// 2. rememberSaveable - 旋转屏幕后保留
var text by rememberSaveable { mutableStateOf("") }

// 3. rememberSaveable + 自定义 Saver
data class User(val name: String, val age: Int)

val userSaver = Saver<User, Bundle>(
    save = { bundleOf("name" to it.name, "age" to it.age) },
    restore = { User(it.getString("name")!!, it.getInt("age")) }
)

var user by rememberSaveable(stateSaver = userSaver) {
    mutableStateOf(User("张三", 25))
}

// 4. rememberUpdatedState - 在 LaunchedEffect 中使用最新值
@Composable
fun TimerScreen(currentTime: Long) {
    val updatedTime by rememberUpdatedState(currentTime)
    
    LaunchedEffect(Unit) {
        while (true) {
            delay(1000)
            println("当前时间: $updatedTime")  // 始终获取最新值
        }
    }
}

四、状态提升(State Hoisting)

4.1 什么是状态提升?

将状态从 Composable 中移到调用方,使组件无状态、可复用、可测试。

4.2 状态提升模式

kotlin

复制代码
// ❌ 未提升状态 - 难以复用和测试
@Composable
fun BadCounter() {
    var count by remember { mutableIntStateOf(0) }
    Column {
        Text("计数: $count")
        Button(onClick = { count++ }) { Text("增加") }
    }
}

// ✅ 状态已提升 - 无状态组件
@Composable
fun GoodCounter(
    count: Int,
    onCountChange: (Int) -> Unit,
    modifier: Modifier = Modifier
) {
    Column(modifier) {
        Text("计数: $count")
        Button(onClick = { onCountChange(count + 1) }) {
            Text("增加")
        }
    }
}

// 使用方管理状态
@Composable
fun CounterScreen() {
    var count by rememberSaveable { mutableIntStateOf(0) }
    GoodCounter(
        count = count,
        onCountChange = { count = it }
    )
}

4.3 状态提升原则

  1. 单一数据源:状态只存一处

  2. 状态向下流动:通过参数传递

  3. 事件向上传递:通过回调传递

  4. 无状态组件:尽量让组件无状态


五、ViewModel 集成

5.1 基本用法

kotlin

复制代码
class CounterViewModel : ViewModel() {
    // 私有可变状态
    private val _count = MutableStateFlow(0)
    // 公开只读状态
    val count: StateFlow<Int> = _count.asStateFlow()
    
    fun increment() {
        _count.value++
    }
}

@Composable
fun CounterScreen(viewModel: CounterViewModel = viewModel()) {
    // 收集 StateFlow 为 Compose 状态
    val count by viewModel.count.collectAsStateWithLifecycle()
    
    Column {
        Text("计数: $count")
        Button(onClick = { viewModel.increment() }) {
            Text("增加")
        }
    }
}

5.2 依赖注入

kotlin

复制代码
// 带参数的 ViewModel
class UserViewModel(
    private val repository: UserRepository
) : ViewModel() {
    // ...
}

// 在 Composable 中获取
@Composable
fun UserScreen(
    viewModel: UserViewModel = viewModel(
        factory = viewModelFactory {
            initializer {
                UserViewModel(AppRepository)
            }
        }
    )
) {
    // ...
}

六、高级状态管理

6.1 复合状态管理

kotlin

复制代码
// 使用 data class 管理相关状态
data class FormState(
    val username: String = "",
    val email: String = "",
    val isLoading: Boolean = false,
    val error: String? = null
)

class FormViewModel : ViewModel() {
    private val _state = MutableStateFlow(FormState())
    val state: StateFlow<FormState> = _state.asStateFlow()
    
    fun updateUsername(username: String) {
        _state.update { it.copy(username = username) }
    }
    
    fun submit() {
        viewModelScope.launch {
            _state.update { it.copy(isLoading = true) }
            try {
                // 提交逻辑
                _state.update { it.copy(isLoading = false, error = null) }
            } catch (e: Exception) {
                _state.update { it.copy(isLoading = false, error = e.message) }
            }
        }
    }
}

6.2 状态容器模式

kotlin

复制代码
// 复杂页面的状态容器
@Stable
class UiStateHolder {
    var tabIndex by mutableIntStateOf(0)
    var searchQuery by mutableStateOf("")
    var selectedItems by mutableStateOf(setOf<String>())
    var isSelectionMode by mutableStateOf(false)
    
    fun toggleSelection(id: String) {
        selectedItems = if (selectedItems.contains(id)) {
            selectedItems - id
        } else {
            selectedItems + id
        }
        isSelectionMode = selectedItems.isNotEmpty()
    }
}

@Composable
fun rememberUiStateHolder(): UiStateHolder {
    return remember { UiStateHolder() }
}

七、副作用与状态

7.1 常用副作用对比

副作用 用途 特点

|--------------------------|--------|----------|
| LaunchedEffect | 执行协程 | 键变化时重启 |
| rememberCoroutineScope | 手动启动协程 | 跟随组合生命周期 |
| DisposableEffect | 清理资源 | 离开时清理 |
| SideEffect | 每次重组后 | 不常用 |
| produceState | 异步生成状态 | 自动管理生命周期 |

7.2 代码示例

kotlin

复制代码
// LaunchedEffect - 进入组合执行一次
@Composable
fun UserScreen(userId: String) {
    var user by remember { mutableStateOf<User?>(null) }
    
    LaunchedEffect(userId) {  // userId 变化时重新执行
        user = repository.getUser(userId)
    }
}

// produceState - 更简洁的异步状态
@Composable
fun loadUser(userId: String): State<User?> {
    return produceState<User?>(initialValue = null, userId) {
        value = repository.getUser(userId)
    }
}

// DisposableEffect - 资源清理
@Composable
fun LifecycleAwareComponent() {
    DisposableEffect(Unit) {
        val listener = createListener()
        addListener(listener)
        onDispose {
            removeListener(listener)  // 离开时清理
        }
    }
}

八、性能优化

8.1 避免不必要重组

kotlin

复制代码
// ❌ 整个对象作为状态
data class User(val name: String, val age: Int, val avatar: String)
var user by remember { mutableStateOf(User(...)) }
// 修改 name 会导致整个 User 变化

// ✅ 使用 @Stable 或拆分状态
@Stable
data class User(val name: String, val age: Int, val avatar: String)

// 或拆分为独立状态
var name by remember { mutableStateOf("") }
var age by remember { mutableIntStateOf(0) }

8.2 使用 derivedStateOf 派生状态

kotlin

复制代码
// ❌ 每次重组都计算
val filteredList = list.filter { it.contains(query) }

// ✅ 只有 query 或 list 变化时才计算
val filteredList by remember {
    derivedStateOf { list.filter { it.contains(query) } }
}

8.3 使用 remember 缓存计算结果

kotlin

复制代码
// ❌ 每次重组都执行
val sortedList = list.sorted()

// ✅ 只有 list 变化时才排序
val sortedList = remember(list) { list.sorted() }

九、完整实战示例

kotlin

复制代码
// 数据模型
data class Todo(
    val id: String,
    val title: String,
    val isDone: Boolean = false
)

// ViewModel
class TodoViewModel : ViewModel() {
    private val _todos = MutableStateFlow<List<Todo>>(emptyList())
    val todos: StateFlow<List<Todo>> = _todos.asStateFlow()
    
    private val _filter = MutableStateFlow("")
    val filter: StateFlow<String> = _filter.asStateFlow()
    
    // 派生状态:过滤后的列表
    val filteredTodos: StateFlow<List<Todo>> = combine(_todos, _filter) { todos, filter ->
        if (filter.isBlank()) todos
        else todos.filter { it.title.contains(filter, ignoreCase = true) }
    }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
    
    fun addTodo(title: String) {
        _todos.update { it + Todo(UUID.randomUUID().toString(), title) }
    }
    
    fun toggleTodo(id: String) {
        _todos.update { todos ->
            todos.map { if (it.id == id) it.copy(isDone = !it.isDone) else it }
        }
    }
    
    fun removeTodo(id: String) {
        _todos.update { it.filterNot { todo -> todo.id == id } }
    }
    
    fun updateFilter(query: String) {
        _filter.value = query
    }
}

// UI 层
@Composable
fun TodoScreen(viewModel: TodoViewModel = viewModel()) {
    val todos by viewModel.filteredTodos.collectAsStateWithLifecycle()
    val filter by viewModel.filter.collectAsStateWithLifecycle()
    
    Column(Modifier.fillMaxSize().padding(16.dp)) {
        // 搜索框
        OutlinedTextField(
            value = filter,
            onValueChange = viewModel::updateFilter,
            modifier = Modifier.fillMaxWidth(),
            placeholder = { Text("搜索待办...") }
        )
        
        Spacer(Modifier.height(16.dp))
        
        // 待办列表
        LazyColumn {
            items(todos, key = { it.id }) { todo ->
                TodoItem(
                    todo = todo,
                    onToggle = { viewModel.toggleTodo(it) },
                    onRemove = { viewModel.removeTodo(it) }
                )
            }
        }
        
        Spacer(Modifier.height(16.dp))
        
        // 添加按钮
        var newTodo by rememberSaveable { mutableStateOf("") }
        Row {
            OutlinedTextField(
                value = newTodo,
                onValueChange = { newTodo = it },
                modifier = Modifier.weight(1f)
            )
            Button(
                onClick = {
                    if (newTodo.isNotBlank()) {
                        viewModel.addTodo(newTodo)
                        newTodo = ""
                    }
                }
            ) {
                Text("添加")
            }
        }
    }
}

// 无状态组件
@Composable
fun TodoItem(
    todo: Todo,
    onToggle: (String) -> Unit,
    onRemove: (String) -> Unit,
    modifier: Modifier = Modifier
) {
    Row(
        modifier = modifier.fillMaxWidth().padding(vertical = 8.dp),
        verticalAlignment = Alignment.CenterVertically
    ) {
        Checkbox(
            checked = todo.isDone,
            onCheckedChange = { onToggle(todo.id) }
        )
        Text(
            text = todo.title,
            modifier = Modifier.weight(1f),
            style = MaterialTheme.typography.bodyLarge,
            textDecoration = if (todo.isDone) TextDecoration.LineThrough else null
        )
        IconButton(onClick = { onRemove(todo.id) }) {
            Icon(Icons.Default.Delete, contentDescription = "删除")
        }
    }
}

十、最佳实践总结

场景 推荐方案

|----------|------------------------------------------------|
| 简单 UI 状态 | remember + mutableStateOf |
| 需要保存的状态 | rememberSaveable |
| 屏幕级状态 | ViewModel + StateFlow |
| 应用级状态 | 依赖注入 + 单例 Repository |
| 列表状态 | mutableStateListOf |
| 复杂状态 | data class + StateFlow |
| 派生状态 | derivedStateOf 或 combine |
| 异步状态 | produceState 或 collectAsStateWithLifecycle |


十一、常见错误与解决方案

错误 1:在 Composable 中创建 ViewModel

kotlin

复制代码
// ❌ 错误:每次重组都创建新实例
@Composable
fun BadScreen() {
    val viewModel = TodoViewModel()  // 错误!
}

// ✅ 正确:使用 viewModel() 函数
@Composable
fun GoodScreen(viewModel: TodoViewModel = viewModel()) {
    // ...
}

错误 2:忘记使用 remember

kotlin

复制代码
// ❌ 错误:每次重组都重置状态
@Composable
fun BadCounter() {
    var count = mutableIntStateOf(0)  // 错误!
}

// ✅ 正确
@Composable
fun GoodCounter() {
    var count by remember { mutableIntStateOf(0) }
}

错误 3:在 LaunchedEffect 中使用旧值

kotlin

复制代码
// ❌ 错误:使用旧值
LaunchedEffect(Unit) {
    while (true) {
        delay(1000)
        println(count)  // 可能使用旧值
    }
}

// ✅ 正确:使用 rememberUpdatedState
val updatedCount by rememberUpdatedState(count)
LaunchedEffect(Unit) {
    while (true) {
        delay(1000)
        println(updatedCount)  // 始终最新值
    }
}

十二、总结

Jetpack Compose 状态管理的核心原则:

  1. 单一数据源:状态只存一处,避免同步问题

  2. 状态向下流动:通过参数传递状态

  3. 事件向上传递:通过回调传递用户操作

  4. 就近原则:状态放在离使用最近的地方

  5. 生命周期感知 :使用 rememberSaveable 和 ViewModel 处理配置变化

  6. 性能优化 :使用 derivedStateOf、remember 等避免不必要计算

掌握这些概念和模式,可以构建出可维护、可测试、高性能的 Compose 应用。

相关推荐
缓慢更新2 小时前
审批的加签与会签:多人意见怎么合
android·java·数据库·低代码·rxjava
我怀里的猫3 小时前
android-activity-launch-sequence
android
YB13755 小时前
Jetpack Compose 动画rememberInfiniteTransition
android
baidu_247438618 小时前
Android 自定义竖直的虚线
android
传奇开心果编程8 小时前
【Jetpack Compose娓娓道来】 第25课:ViewModel就是UI界面的大脑
android·kotlin·移动开发·jetpack compose·编程语言理论·编程框架·声明式ui
马立杰9 小时前
PHP 未授权 RCE 应急响应:CVE-2026-109618 CVE-2026-109619 排查与加固实战
android·开发语言·php
NamoAmitabha1289 小时前
安装“编译器环境”(Android Studio)--deepseek--Cocos --Creator3.8.8
android·ide·android studio
聚美智数10 小时前
文档转换 API‑文档转 PDF‑PDF 转文档‑异步文档处理‑文件格式迁移‑转换结果回调推送 API 接口介绍
android·java·pdf
booboosui10 小时前
Android 上的无人回合:只放开 2 类工具,宁可空手
android