1. 核心概念:State Hoisting 定义、状态与事件分离、单向数据流原则

State Hoisting(状态提升)是 Jetpack Compose 中用于管理 UI 状态的核心架构模式。它要求将组件的状态与其展示逻辑分离,将状态“提升”到父级或外部可观察容器中,从而保证子组件的可复用性与可测试性。

1.1 定义与三个支柱

支柱说明代码体现
状态与事件分离状态由调用方持有,事件回调由调用方提供;组件自身不存储可变数据状态通过函数参数传入,回调通过 lambda 参数传出
单向数据流状态从父流向子(数据下沉),事件从子流向父(事件上浮),形成闭环父组件修改状态 → 重组子组件;子组件触发回调 → 父组件更新状态
状态提升将 remember 或 mutableStateOf 从子级移至父级,使子组件变成“纯展示”
@Composable
fun Counter(count: Int, onIncrement: () -> Unit) {
    Button(onClick = onIncrement) { ... }
}

1.2 原理图解:状态提升的单向数据流

下图展示了 State Holder(状态持有者) 与 Composable(展示组件) 之间的数据流向。状态始终从外部流入,事件始终从内流出,组件内部没有可变状态。

State Hoisting 单向数据流 父组件(State Holder) remember { mutableStateOf(...) } 子组件(Pure Display) 接收状态 + 回调 状态下沉 事件上浮 单向数据流:状态只在父组件中修改,子组件通过回调通知父组件。 子组件永不直接修改状态,保证了组件的可复用性和可预测性。 🌱 生活类比:点单系统 • 顾客(父组件)持有菜单和库存(状态)。 • 厨师(子组件)只知道当前菜品的展示,不改变库存。 • 顾客通过“加菜”事件(回调)增加订单,厨师仅做展示。

1.3 常见误区

❌ 误区 1:把状态全部放在 ViewModel 就完事了
即使使用了 ViewModel,若在 Composable 中直接调用 mutableStateOf 而不提升到父组件,仍然会破坏单向数据流。ViewModel 管理业务状态,但UI 状态提升关注的是组件间的数据依赖。
❌ 误区 2:事件回调命名混乱
常见反模式:子组件回调命名为 onValueChanged,但实际内部直接修改外部状态。应严格遵循“组件只催缴事件,不触摸状态”原则。
❌ 误区 3:状态提升过度
所有状态都堆到顶层会导致参数爆炸和性能浪费。只提升需要多个子组件共享的状态,私有状态应保留在局部。

1.4 区分场景:何时提升状态

场景是否提升原因
状态仅被一个子组件使用不提升局部状态更简单,避免不必要的重组
状态被多个兄弟组件共享提升到公共父组件保证数据源唯一
状态需要持久化或跨屏幕共享提升到 ViewModel 或全局 store生命周期管理

1.5 练习题

  1. 判断题:State Hoisting 要求子组件不能持有任何 remember 状态。 ( )
  2. 补全代码:下列组件缺失哪个部分才能符合“状态提升”模式?
@Composable
fun ColorPicker(
    selectedColor: Color,   // 状态由外部传入
    // 此处应添加什么参数?
) {
    Canvas(modifier = Modifier.fillMaxSize()) {
        drawRectangle(color = selectedColor)
    }
}
💡 提示 练习 2 的正确答案:添加 onColorChange: (Color) -> Unit 回调,让外部更新颜色状态。

1.6 应用边界

State Hoisting 并非银弹,存在明确的边界:

  • 性能边界:过度提升会导致频繁的全局重组。应当将不常变化的状态保留在局部。
  • 复杂度边界:当组件层级超过 4 层且状态传递需要跨多级时,考虑使用 CompositionLocal 或 StateFlow 代替显式参数传递。
  • 生命周期边界:与屏幕生命周期绑定的状态(如加载状态)应提升到 ViewModel,而非记忆在 Composable 中。