2. 无状态组件与有状态组件分离实践

本章聚焦于 Compose 状态管理的核心设计模式——状态提升。我们将学习如何识别有状态组件,并将其重构为纯无状态(Stateless)Composable 函数,由外部通过 state 参数和 onEvent 回调驱动。这是构建可测试、可复用 UI 组件的基石。

2.1 核心模式:状态提升

状态提升的本质是将 Composable 内部的局部状态移动(提升)至其调用方,使该 Composable 本身变为无状态。这遵循了“数据向下,事件向上”的单向数据流原则。

  • 无状态组件: 不拥有任何 remember 或 mutableStateOf,仅通过参数接收状态,通过回调发出事件。
  • 有状态组件: 内部维护局部状态,直接修改并消费。

2.2 核心差异对比

维度有状态 Composable无状态 Composable(纯)
状态所有权内部拥有(remember)外部传入(函数参数)
可重用性低(逻辑硬编码)高(逻辑由调用方决定)
可测试性困难(内部状态不可见)容易(纯函数,输出由输入决定)
参数格式无特定状态参数value: T, onEvent: (T) -> Unit
数据流内部闭环单向数据流(向下传递,向上通知)

2.3 实现流程:从有状态重构为无状态

  1. 识别状态: 找到组件内部使用 remember 和 mutableStateOf 声明的变量。
  2. 提取参数: 将状态值定义为函数参数,将状态更新逻辑定义为事件回调参数(如 onValueChange)。
  3. 提升状态: 将 remember 调用移至父级(调用方)Composable。
  4. 建立连接: 在父级将状态向下传递,并接收子级的事件更新状态。

2.4 代码示例:纯 Composable 函数

反模式(有状态,不可复用):

@Composable
fun MyBadTextField() {
    var text by remember { mutableStateOf("") }
    TextField(value = text, onValueChange = { text = it })
}

正确模式(无状态,纯 Composable):

// 纯组件:不管理任何状态,仅通过参数接收并转发
@Composable
fun MyPureTextField(
    text: String,            // 外部传入的当前状态
    onEvent: (String) -> Unit // 外部传入的事件回调
) {
    TextField(value = text, onValueChange = onEvent)
}

// 调用方(状态持有者)
@Composable
fun MyScreen() {
    var text by remember { mutableStateOf("") }
    
    // 数据向下传递,事件向上传递
    MyPureTextField(
        text = text,
        onEvent = { newValue -> text = newValue }
    )
}
关键解读:
MyPureTextField 不拥有、不管理、不修改状态。它仅展示传入的 text 并通过 onEvent 将用户输入转发出去。这使得该组件可以在任意场景下复用。
无状态组件架构:状态提升模式 调用方 Composable (状态持有者) remember mutableStateOf 无状态 Composable (纯 UI 组件) 渲染 UI onEvent 回调 持有 state 渲染 触发 事件传递 💡 数据通过参数向下流动,事件通过回调向上流动。 无状态组件不具备副作用,完全由外部驱动,大幅提升可复用性与可测试性。

2.5 常见错误与调试

❌ 错误:状态不同步
如果调用方没有正确 remember 状态,或者在传递时使用了错误的副本,UI 将无法响应变化。例如,直接传递一个局部变量而非状态引用。
✅ 调试技巧:
使用 Android Studio 的 Layout Inspector 检查组件的参数值。如果 UI 展示的内容与外部状态不一致,检查状态提升链路是否正确,确认状态对象是否被重组触发更新。

2.6 最佳实践

  • 坚持“一个 Compose 组件要么全有状态,要么全无状态”,避免混用。
  • 为事件回调使用语义化命名:onEvent、onSearch、onItemClick。
  • 将状态定义在离消费点最近的地方,但务必保持在纯组件之外。
  • 初期从有状态组件开始快速开发,上线前重构为无状态模式以提高复用性。