Composable 与状态

要点

remember 负责保留值,mutableStateOf 负责被观察的读写。重组重复计算界面描述,不应该在计算过程中发请求或改业务数据。

目录


什么是 @Composable

@Composable 不是一个类,只是函数上的注解,但它改变了函数的能力:

@Composable
fun MessageCard(name: String) {
    Text("Hello, $name")
}

三条心智规则:

  1. 函数即 UI: 调用 MessageCard("Ada") 就是把它的内容”挂”到界面树上,可任意嵌套组合
  2. 幂等可重入: Compose 可能随时重新调用这个函数(重组),同一输入应产生同一界面——所以不要在函数体内写副作用(弹 toast、改数据库)
  3. 顺序执行: 函数体按顺序描述界面,类似”DSL 形式的布局文件”

💡 与 React 类比:@Composable ≈ 函数组件,Modifier ≈ props 中的样式,重组 ≈ re-render。


第一个可交互组件:计数器

直接用普通变量”记”状态是无效的,看正确写法:

@Composable
fun Counter() {
    // remember:让值跨重组存活;mutableStateOf:让值的变化可被 Compose 观察
    var count by remember { mutableStateOf(0) }

    Column {
        Text("点击了 ${count} 次")
        Button(onClick = { count++ }) {
            Text("点我")
        }
    }
}

运行后每点一次按钮,文本自动更新。这背后发生了什么:

  1. count++ 改变了 MutableState 的值
  2. Compose 观察到”读取该状态的 Composable”需要更新
  3. 重新执行 Counter() 函数体(重组),Text 拿到新的 count 渲染
  4. remember 返回缓存中的同一状态对象,而不是重新初始化为 0

by 委托语法让我们直接读写 .value,比 count.value++ 简洁(详见泛型与委托属性)。


重组 Recomposition

定义: 状态变化后,Compose 重新执行受影响的 Composable 函数以更新界面树的过程。

关键特性:

特性说明
智能范围使读取状态的相关重组作用域失效;实际执行与跳过还取决于编译器、稳定性和读取位置
可跳过符合跳过条件的 Composable 可能被跳过;比较与稳定性规则取决于编译配置,不能据此保证执行次数
可能频繁动画、滚动、输入每帧都可能触发,因此 Composable 应轻量
顺序不定不要依赖多个 Composable 的执行顺序,也不要依赖重组次数
@Composable
fun Profile(name: String, age: Int) {
    Column {
        Header(name)              // 若 name 没变,Header 可被跳过
        AgeBadge(age)             // 若 age 变了,只有它重新执行
    }
}

📖 状态 API(State/derivedStateOf/snapshotFlow 等)的完整字典见 Compose 状态 API 详解。


remember 与状态持久性

remember 的作用域是”该组件在界面树上的存在期”:

var query by remember { mutableStateOf("") }                    // 重组间存活
var query2 by rememberSaveable { mutableStateOf("") }           // 还能存活于配置变更/进程恢复
  • remember: 重组不丢,但旋转屏幕、进程被杀后丢失
  • rememberSaveable: 自动把值写入 Bundle,能扛住配置变更(需可序列化类型)
  • ViewModel: 需要跨配置变更且包含业务逻辑的状态可放到 ViewModel;ViewModel 本身不保证进程重建后恢复(见协程与 Flow 基础)

一个常见递进:简单 UI 状态用 remember → 需要跨配置变更 → rememberSaveable → 涉及业务 → ViewModel + StateFlow。


状态提升

原则: 把状态从子组件”提升”到调用方,子组件变成无状态(stateless)组件。

// ❌ 有状态组件:内部锁死状态,难以复用与测试
@Composable
fun NameInputBad() {
    var name by remember { mutableStateOf("") }
    OutlinedTextField(value = name, onValueChange = { name = it })
}

// ✅ 无状态组件:状态由外部传入,事件向上回调
@Composable
fun NameInput(
    name: String,                          // 状态下行(参数)
    onNameChange: (String) -> Unit,        // 事件上行(回调)
    modifier: Modifier = Modifier
) {
    OutlinedTextField(
        value = name,
        onValueChange = onNameChange,
        modifier = modifier
    )
}

// 调用方持有状态,可同时服务多个场景(预览、表单、撤销栈……)
@Composable
fun Form() {
    var name by remember { mutableStateOf("") }
    NameInput(name = name, onNameChange = { name = it })
}

这就是 单向数据流(UDF): 状态向下流动、事件向上传递。后续 ViewModel + StateFlow 的架构正是这个模式在页面级的放大。


常见误区

误区一:在组合期间写状态 → 无限重组死循环

@Composable
fun Broken() {
    var count by remember { mutableStateOf(0) }
    count++          // ❌ 组合中直接改状态:改完触发重组 → 再改 → 死循环崩溃
    Text("$count")
}

修正:由事件、业务状态持有者或合适的副作用 API 发起变化,避免在描述 UI 时直接写入自己正在读取的状态。

误区二:普通变量当状态用

@Composable
fun AlsoBroken() {
    var count = 0                     // ❌ 每次重组都重新初始化为 0,界面永远不变
    Button(onClick = { count++ }) { Text("$count") }
}

误区三:副作用直接写在函数体里

发送网络请求、写数据库等操作请放入 LaunchedEffect/ViewModel,明确工作生命周期:LaunchedEffect 在进入组合及 key 改变时启动,离开组合时取消;ViewModel 的工作遵循自身作用域。

更多疑难现象(重组死循环、状态丢失、协程泄漏)的排查手册见故障排除速查。


练习与实践

基础练习

  • [ ] 复现计数器,并新增”重置”按钮把 count 归零
  • [ ] 把计数器改造成无状态组件 Counter(count: Int, onIncrement: () -> Unit),在父组件中持有状态
  • [ ] 用 OutlinedTextField + remember 实现一个”输入即显示字数”的组件
  • [ ] 把计数器的 remember 换成 rememberSaveable,旋转屏幕对比状态是否保留

进阶挑战

  • [ ] 实现一个待办输入框:状态为 data class Draft(val title: String, val done: Boolean),验证输入后界面正确刷新
  • [ ] 故意写出组合期间写状态的死循环代码,观察 Logcat 报错信息并记录修复方法
  • [ ] 用两个并排的 NameInput 共享同一个状态源,体会状态提升带来的复用性

相关文档

暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇