🧩 重组范围 & 稳定性 Compose 性能优化

📐 重组范围 ⚡ 稳定 / 非稳定 🏷️ @Stable / @Immutable
🔍 重组范围 · 最小化更新 Recomposition Scope

Compose 只重组 读取了已变化状态 的代码块(lambda / composable 作用域)。 重组范围 = 读取状态的最小可组合函数或内联块。避免“父重组导致子全部重组”。

📌 原则

重组以 @Composable 函数为边界,但内联 lambda 会扩大范围。将状态读取下沉到子项。

🧪 示例

若 Text(text) 读取了 text,只有该 Text 重组,兄弟不受影响。

⚠️ 常见陷阱

在顶层 Composable 读取大量状态 → 整个界面重组。使用派生状态、lambda 包裹。

// ✅ 重组范围最小化:只有读取 count 的 Text 重组
@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }
    Button(onClick = { count++ }) { Text("➕") }
    Text("点击次数: $count")          // 仅这里重组
    HeavyStaticComponent()            // 不重组
}
⚖️ 稳定类型 vs 非稳定类型 Stable / Unstable

Compose 编译器通过“稳定性”判断是否需要跳过重组。
稳定类型:编译器知道其是否变化(equals 或引用)。非稳定类型:编译器无法推断,每次均重组。

分类说明常见类型
✅ 稳定 (Stable)不可变或 Compose 可追踪变化原始类型、String、Lambda、MutableState、@Immutable data class
⚠️ 非稳定 (Unstable)可变且未标记,编译器保守处理普通 List、Map、普通 data class (未加注解)、接口、抽象类
稳定类型 Stable 编译器可追踪 非稳定类型 Unstable 每次重组 编译器推断 @Stable / @Immutable
🏷️ @Stable 与 @Immutable 注解 契约 · 跳过重组

🔹 @Immutable

表示类型所有公开属性在构造后永不改变(深度不可变)。编译器可完全跳过相等性检查。

@Immutable
data class User(val name: String, val age: Int)

🔸 @Stable

表示类型对外表现“稳定”,即使内部可变,但 Compose 能通过快照系统感知变化。常用于可变但受控的类。

@Stable
class MutableUiState {
    var name by mutableStateOf("")
    var age by mutableStateOf(0)
}

💡 最佳实践:数据类加 @Immutable;若类包含 MutableState 或快照属性,使用 @Stable。避免编译器将你的类标记为非稳定。

📋 注解对比速查
注解可变性编译器行为典型用例
@Immutable完全不可变跳过重组(恒等/equals)data class, 配置, 常量
@Stable允许受控可变信任快照通知,跳过不必要重组ViewModel 状态, 带 mutableStateOf 的类
无注解可变 / 不确定视为非稳定,每次重组普通 List, 可变集合, 接口
🧭 重组决策流程 · 稳定性影响
状态变化 稳定性检查 重组 / 跳过 触发 稳定? 是→跳过 / 否→重组