一、先给一句"硬核定义"(先放结论)
重组(Recomposition)= 当状态变化时,Compose 重新执行「受影响的 Composable 函数」,以计算新的 UI 描述
注意三点关键词:
- 重新执行函数
- 只执行受影响的部分
- 不是重新创建整个界面
二、最重要的认知纠正(非常关键)
❌ 错误理解
"重组 = 重新绘制 View"
✅ 正确理解
重组 = 重新"算" UI,而不是重新"画" UI
Compose 的流程是:
text
State 变化
↓
Recomposition(重新执行 Composable 函数)
↓
生成新的 UI Tree(描述)
↓
Diff
↓
真正需要的地方才重绘
三、一个最直观的例子(必须看)
kotlin
@Composable
fun Counter() {
Log.d("Counter", "Counter 执行")
var count by remember { mutableStateOf(0) }
Button(onClick = { count++ }) {
Text("count = $count")
}
}
日志行为
text
首次进入页面
→ Counter 执行
点击按钮
→ Counter 执行
再点击
→ Counter 执行
👉 函数反复执行 = 重组
四、为什么 Compose 要"重组"?(本质原因)
因为 Compose 的核心哲学是:
kotlin
UI = f(State)
- State 变了
- 就必须重新算 f(State)
👉 这是声明式 UI 的必然结果
五、那「成功重组」到底是什么意思?
这是问的第二个重点 👇
"在每次成功重组后,执行同步操作(SideEffect)"
我们拆开解释。
1️⃣ 什么叫「成功重组」?
不是每一次函数执行都算"成功重组"。
成功重组 =
Compose 已经完成了一次状态 → UI 描述的更新,并且这次更新被提交
换句话说:
- 新 UI Tree 计算完成
- Diff 成功
- 没有被中断 / 回滚
2️⃣ SideEffect 的执行时机(关键)
kotlin
SideEffect {
// 同步副作用
}
执行顺序是:
text
Recomposition(函数执行)
↓
Compose 内部完成 UI 更新
↓
✅ SideEffect 执行
👉 SideEffect 永远不会在"中途"执行
六、为什么要"重组成功后"才执行 SideEffect?
因为 副作用是不可回滚的。
比如:
kotlin
SideEffect {
systemUiController.setStatusBarColor(Color.Black)
}
如果:
- UI 计算了一半
- 又被新的状态打断
👉 如果这时执行副作用,就会产生 状态与 UI 不一致
所以 Compose 保证:
SideEffect 一定在 UI 状态已经稳定之后执行
七、SideEffect ≠ 初始化(很多人误用)
❌ 错误用法
kotlin
SideEffect {
viewModel.loadData() // ❌
}
原因:
- 每次重组都会执行
- 会重复触发
✅ 正确用途示例
kotlin
SideEffect {
Log.d("Compose", "当前 UI 状态已经同步")
}
kotlin
SideEffect {
window.statusBarColor = Color.Black
}
👉 让"外部世界"跟 UI 状态保持一致
八、重组 vs SideEffect 的"时间线图"(非常重要)
text
State 变化
↓
Composable 函数重新执行(重组)
↓
Compose 内部应用 UI 更新
↓
✔ SideEffect 执行
⚠️ SideEffect 不参与 UI 计算
九、一个常见误解:重组是不是性能问题?
答案:不是
重组是常态,不是异常
真正的性能问题是:
- 重组范围过大
- 不必要的重组
- 重组中做了重活
十、应该如何"正确看待重组"?
现在的角色转变很重要:
❌ "怎么避免重组?"
✅ "如何让重组只发生在该发生的地方?"
这也是:
derivedStateOf- 状态拆分
- key 的意义所在
十一、给一句工程级总结(很重要)
重组不是 UI 的问题,而是 State 设计的问题
再给一句关于 SideEffect 的「精华版理解」
SideEffect 是"在 UI 已经稳定之后,对外部世界做同步动作"