跳到主要内容

Compose 状态管理

问题

Compose 中如何管理状态?状态提升(State Hoisting)是什么?

答案

1. 状态基础

// mutableStateOf - 创建可观察状态
var name by remember { mutableStateOf("") }

// mutableIntStateOf - 基本类型优化版本(避免装箱)
var count by remember { mutableIntStateOf(0) }

// rememberSaveable - 配置变更(如旋转)后仍保持状态
var text by rememberSaveable { mutableStateOf("") }

2. 状态提升(State Hoisting)

将状态从子组件提升到父组件,使子组件成为无状态的:

// ❌ 有状态的组件(不利于复用和测试)
@Composable
fun SearchBar() {
var query by remember { mutableStateOf("") }
TextField(value = query, onValueChange = { query = it })
}

// ✅ 无状态组件(状态提升到父组件)
@Composable
fun SearchBar(
query: String, // 状态下传
onQueryChange: (String) -> Unit // 事件上传
) {
TextField(value = query, onValueChange = onQueryChange)
}

// 父组件持有状态
@Composable
fun SearchScreen() {
var query by remember { mutableStateOf("") }
SearchBar(query = query, onQueryChange = { query = it })
SearchResults(query)
}
状态提升原则

将状态提升到最低公共祖先:需要共享状态的 Composable 的最近公共父级。

3. ViewModel 集成

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

fun updateName(name: String) {
_uiState.update { it.copy(name = name) }
}
}

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

@Composable
fun UserScreen(viewModel: UserViewModel = viewModel()) {
// collectAsStateWithLifecycle - 生命周期感知的状态收集
val uiState by viewModel.uiState.collectAsStateWithLifecycle()

UserContent(
name = uiState.name,
isLoading = uiState.isLoading,
onNameChange = viewModel::updateName
)
}

4. 状态持有者模式

对于复杂的 UI 逻辑,使用状态持有者类(不是 ViewModel):

// 状态持有者 - 管理 UI 相关逻辑
class SearchBarState(
initialQuery: String = ""
) {
var query by mutableStateOf(initialQuery)
private set

var isExpanded by mutableStateOf(false)
private set

fun updateQuery(newQuery: String) {
query = newQuery
}

fun toggleExpand() {
isExpanded = !isExpanded
}
}

@Composable
fun rememberSearchBarState(initialQuery: String = "") = remember {
SearchBarState(initialQuery)
}

@Composable
fun SearchScreen() {
val searchState = rememberSearchBarState()
SearchBar(state = searchState)
}

5. derivedStateOf

当状态需要从其他状态派生时使用,避免不必要的重组:

@Composable
fun FilteredList(items: List<String>) {
var query by remember { mutableStateOf("") }

// ✅ 只在 items 或 query 变化时重新计算
val filteredItems by remember(items) {
derivedStateOf {
items.filter { it.contains(query, ignoreCase = true) }
}
}

TextField(value = query, onValueChange = { query = it })
LazyColumn {
items(filteredItems) { item -> Text(item) }
}
}

常见面试问题

Q1: rememberrememberSaveable 的区别?

答案

  • remember:在重组时保持值,但 Activity 重建(如旋转屏幕)时丢失
  • rememberSaveable:在 Activity 重建和进程死亡后恢复时也能保持值(内部使用 SavedStateHandle

rememberSaveable 默认支持 Bundle 能保存的类型。自定义类型需要实现 Saver 或使用 @Parcelize

Q2: Compose 中应该在哪里管理状态?

答案

状态类型位置示例
临时 UI 状态Composable 内 rememberTextField 输入、动画状态
屏幕级 UI 状态状态持有者类 + rememberScaffold 状态、搜索栏展开
业务逻辑状态ViewModel + StateFlow网络数据、用户信息
跨屏幕状态ViewModel + Navigation登录状态、全局设置

Q3: collectAsStatecollectAsStateWithLifecycle 的区别?

答案

  • collectAsState():始终收集 Flow,即使 App 在后台
  • collectAsStateWithLifecycle():只在生命周期至少是 STARTED 时才收集。App 进入后台时自动停止收集,节省资源

推荐始终使用 collectAsStateWithLifecycle()

Q4: 什么是单向数据流(Unidirectional Data Flow)?

答案

Compose 推荐的状态管理模式:状态向下流动,事件向上流动

ViewModel → State ↓ UI Composable → Event ↑ ViewModel
  • 状态(State):从 ViewModel 向下传递给 Composable
  • 事件(Event):从 Composable 向上传递给 ViewModel(通过 lambda 回调)

好处:状态变化可追溯、UI 可预测、易于测试。

Q5: snapshotFlow 的作用是什么?

答案

snapshotFlow 将 Compose 状态转换为 Flow,当状态变化时发射新值:

LaunchedEffect(listState) {
snapshotFlow { listState.firstVisibleItemIndex }
.distinctUntilChanged()
.collect { index ->
// 监听滚动位置变化
analytics.trackScroll(index)
}
}

适用于需要将 Compose 状态桥接到 Flow 链式处理的场景。

相关链接