7.1 什么是状态提升(State Hoisting)
状态提升:将 Composable 内部的状态移到它的调用方中管理。
听起来简单,但这是 Compose 架构设计中最重要、最基础的模式。
为什么需要状态提升?
kotlin
// ❌ 没有状态提升 ------ 状态在组件内部,外部无法控制
@Composable
fun MyTextField() {
var text by remember { mutableStateOf("") }
TextField(
value = text,
onValueChange = { text = it }
)
}
// 使用方无法:
// 1. 读取输入内容
// 2. 设置默认值
// 3. 监听输入变化
kotlin
// ✅ 状态提升 ------ 状态由调用方管理
@Composable
fun MyTextField(
text: String, // 状态从外部传入
onTextChange: (String) -> Unit // 事件向上通知
) {
TextField(
value = text,
onValueChange = onTextChange
)
}
// 使用方可以:
@Composable
fun SignUpForm() {
var email by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
Column {
MyTextField(
text = email,
onTextChange = { email = it }
)
MyTextField(
text = password,
onTextChange = { password = it }
)
Button(
onClick = { submit(email, password) },
enabled = email.isNotBlank() && password.isNotBlank()
) {
Text("Submit")
}
}
}
7.2 状态提升的标准模式
Compose 组件遵循一个被广泛认可的命名约定:
kotlin
// 标准状态提升接口
@Composable
fun ExpandableCard(
expanded: Boolean, // 状态(当前是什么)
onExpandedChange: (Boolean) -> Unit, // 事件(变化通知)
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
Surface(
modifier = modifier.clickable { onExpandedChange(!expanded) }
) {
Column {
// 根据状态决定显示方式
if (expanded) {
content()
}
// 展开/收起指示器
Icon(
if (expanded) Icons.Default.ExpandLess
else Icons.Default.ExpandMore,
contentDescription = "Toggle"
)
}
}
}
状态提升的三条规则
| 规则 | 说明 |
|---|---|
| 状态上移 | 状态至少提升到所有需要读取它的组件最近的共同父级 |
| 状态不可变暴露 | 对外暴露 val 状态,而非 MutableState |
| 事件回调 | 状态变化通过回调通知父级,遵循单向数据流 |
7.3 单向数据流(UDF)
Unidirectional Data Flow 是 Compose 架构设计的基础模式:

核心原则:
- 数据单向流动:状态从父级流向子级(通过参数)
- 事件单向流动:事件从子级向上通知父级(通过回调)
- 单一可信源:状态只在一个地方定义和修改
kotlin
// ViewModel ------ 状态的源头
class SearchViewModel : ViewModel() {
private val _uiState = MutableStateFlow(SearchUiState())
val uiState: StateFlow<SearchUiState> = _uiState.asStateFlow()
fun onQueryChanged(query: String) {
_uiState.update { it.copy(query = query) }
}
fun onSearch() {
// 发起搜索请求...
}
}
data class SearchUiState(
val query: String = "",
val results: List<Result> = emptyList(),
val isLoading: Boolean = false
)
// Composable ------ 只消费状态和发送事件
@Composable
fun SearchScreen(viewModel: SearchViewModel = viewModel()) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
Column {
SearchBar(
query = uiState.query, // 状态向下传递
onQueryChange = viewModel::onQueryChanged, // 事件向上传递
onSearch = viewModel::onSearch
)
// ...
}
}
7.4 Compose 中的无状态 vs 有状态组件
这是组件设计的重要分类:
无状态组件(Stateless)
kotlin
// 纯展示,所有数据通过参数传入
@Composable
fun UserProfile(
name: String, // 外部管理状态
avatarUrl: String,
isOnline: Boolean,
onProfileClick: () -> Unit,
modifier: Modifier = Modifier
) {
Surface(
modifier = modifier.clickable(onClick = onProfileClick)
) {
Row {
AsyncImage(model = avatarUrl, contentDescription = null)
Column {
Text(name)
OnlineIndicator(isOnline)
}
}
}
}
优点:完全可复用、可预测、易于测试。
有状态组件(Stateful)
kotlin
// 内部持有状态 ------ 适用于简单的自包含组件
@Composable
fun ExpandableText(
text: String,
maxLines: Int = 3
) {
var expanded by remember { mutableStateOf(false) }
Column {
Text(
text = text,
maxLines = if (expanded) Int.MAX_VALUE else maxLines
)
TextButton(onClick = { expanded = !expanded }) {
Text(if (expanded) "Show less" else "Show more")
}
}
}
优点:使用方便,调用方无需关心内部状态。
设计原则

kotlin
// 最佳实践:同时提供有状态和无状态两个版本
// 无状态版本
@Composable
fun LikeButton(
isLiked: Boolean,
onLikeChange: (Boolean) -> Unit,
modifier: Modifier = Modifier
) {
IconButton(onClick = { onLikeChange(!isLiked) }) {
Icon(
imageVector = if (isLiked) Icons.Default.Favorite
else Icons.Default.FavoriteBorder,
contentDescription = "Like",
tint = if (isLiked) Color.Red else Color.Gray
)
}
}
// 有状态包装
@Composable
fun LikeButton(
initialLiked: Boolean = false,
modifier: Modifier = Modifier
) {
var isLiked by remember { mutableStateOf(initialLiked) }
LikeButton(
isLiked = isLiked,
onLikeChange = { isLiked = it },
modifier = modifier
)
}
7.5 状态提升的层级决策
状态应该放在哪个层级?

决策流程图:

7.6 实际案例:搜索页面
kotlin
// 1. ViewModel ------ 所有状态的单一可信源
class ProductViewModel : ViewModel() {
private val _uiState = MutableStateFlow(ProductUiState())
val uiState: StateFlow<ProductUiState> = _uiState.asStateFlow()
fun onSearchQueryChanged(query: String) {
_uiState.update { it.copy(searchQuery = query) }
}
fun onSearch() {
val query = _uiState.value.searchQuery
if (query.isBlank()) return
_uiState.update { it.copy(isSearching = true) }
viewModelScope.launch {
try {
val results = repository.search(query)
_uiState.update { it.copy(results = results, isSearching = false) }
} catch (e: Exception) {
_uiState.update { it.copy(error = e.message, isSearching = false) }
}
}
}
fun onClearSearch() {
_uiState.update { ProductUiState() }
}
}
data class ProductUiState(
val searchQuery: String = "",
val results: List<Product> = emptyList(),
val isSearching: Boolean = false,
val error: String? = null
)
// 2. Screen ------ 单向数据流的消费端
@Composable
fun ProductScreen(viewModel: ProductViewModel = viewModel()) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
Column(modifier = Modifier.fillMaxSize()) {
// 状态向下
SearchBar(
query = uiState.searchQuery,
// 事件向上
onQueryChange = viewModel::onSearchQueryChanged,
onSearch = viewModel::onSearch,
onClear = viewModel::onClearSearch
)
when {
uiState.isSearching -> LoadingIndicator()
uiState.error != null -> ErrorView(uiState.error!!)
else -> ProductList(uiState.results)
}
}
}
7.7 本章小结
| 概念 | 要点 |
|---|---|
| 状态提升 | 将状态移到调用方,通过参数传回 |
| 无状态组件 | 外部管理状态,内部只显示和回调 |
| 有状态组件 | 内部持有状态,适用于简单自包含场景 |
| 单向数据流 | 状态向下传、事件向上传 |
| 单一可信源 | 状态定义在一处,避免多份副本导致的数据不一致 |
| 层级决策 | 全局→页面→组件→子组件,逐级下放 |