3. 实现 state + onEvent 模式:定义可观察状态、回调事件 lambda
3.1 原理:单向数据流的支柱
在 Compose 状态提升中,state 与 onEvent 构成一对不可分割的契约:
- state —— 可观察的数据源,通常由
mutableStateOf()创建,持有当前值并触发重组。 - onEvent —— 回调 lambda,子 Composable 通过它向父组件发出“我想要改变状态”的信号,却不直接修改状态。
这种模式确保数据流向唯一:数据向下,事件向上。父组件拥有状态的唯一修改权,子组件只负责展示和触发回调。
3.2 实现流程
- 在父 Composable 中用
remember { mutableStateOf(initialValue) }定义可观察状态。 - 将状态值作为参数传给子 Composable。
- 在父 Composable 中定义一个 lambda,用于更新状态(例如
{ newValue -> state.value = newValue })。 - 将 lambda 作为另一个参数传给子 Composable。
- 子 Composable 在用户交互(如点击、输入)时调用该 lambda。
3.3 代码示例:计数器
@Composable
fun CounterScreen() {
var count by remember { mutableStateOf(0) } // 可观察状态
// 事件回调 lambda
val onIncrement: () -> Unit = { count++ }
CounterDisplay(
count = count,
onIncrement = onIncrement
)
}
@Composable
fun CounterDisplay(
count: Int,
onIncrement: () -> Unit
) {
Column(modifier = Modifier.padding(16.dp)) {
Text("当前计数: $count")
Button(onClick = onIncrement) {
Text("增加")
}
}
}
在这个例子中:
CounterScreen定义count和onIncrement。CounterDisplay通过参数接收count(只读)和onIncrement(回调),不直接持有或修改状态。
3.4 角色对比
| 角色 | Compose 对应 | 职责 |
|---|---|---|
| State | mutableStateOf() |
持有可变数据,变化时自动触发重组 |
| onEvent | typealias OnEvent = (T) -> Unit |
将子组件意图转发给父组件,不直接修改数据 |
| 父 Composable | 自带 @Composable |
定义状态、创建 lambda、向下传递 |
| 子 Composable | 参数列表包含 state 和 onEvent | 展示状态、在交互时调用 onEvent |
3.5 调试与常见错误
- ❌ 子组件直接修改传入的状态: 例如直接调用
count++会导致编译错误或不可预期行为。解决方案:必须使用回调。 - ❌ 忘记包裹
remember:mutableStateOf若未用remember包裹,重组时会丢失状态值。始终使用remember { mutableStateOf() }。 - ✅ 调试技巧: 在 lambda 中添加日志,观察回调触发次数;使用
LayoutInspector查看重组范围。
3.6 性能与稳定性优化
- 稳定 lambda: 用
remember包裹回调,避免每次重组都创建新对象:val onIncrement = remember { { count++ } }。 - 避免闭包陷阱: 如果 lambda 依赖外部变量,使用
snapshotFlow或derivedStateOf减少不必要的回调创建。 - 参数稳定性: 将状态拆分为细粒度
State对象,只传递子组件真正需要的部分,减少重组范围。
3.7 state + onEvent 工作流
3.8 总结
state + onEvent 是 Compose 状态提升的基石。通过明确分离状态所有权和事件响应的职责,代码变得可预测、可测试,并且天然支持 UI 的响应式更新。下一阶段我们会基于此模式构建更复杂的表单与列表交互。