跳到主要内容

Composable 函数与重组

问题

Composable 函数是什么?Recomposition(重组)的机制是怎样的?

答案

1. Composable 函数基础

@Composable 注解标记的函数可以发射 UI 元素:

@Composable
fun Greeting(name: String) {
Text(text = "Hello, $name!")
}

@Composable
fun UserCard(user: User) {
Card(modifier = Modifier.padding(8.dp)) {
Column(modifier = Modifier.padding(16.dp)) {
Text(text = user.name, style = MaterialTheme.typography.titleMedium)
Text(text = user.email, style = MaterialTheme.typography.bodyMedium)
}
}
}
Composable 函数的约束
  • 只能在其他 @Composable 函数或 Composition 上下文中调用
  • 没有返回值(返回 Unit),而是发射 UI 节点
  • 可以任意次数、任意顺序、在任何线程被重新执行
  • 不应包含副作用(网络请求、数据库操作等)

2. Recomposition(重组)机制

当 Composable 读取的状态发生变化时,Compose 运行时会重新执行该 Composable 函数,这个过程称为重组。

@Composable
fun Counter() {
var count by remember { mutableStateOf(0) } // 状态

Button(onClick = { count++ }) { // 修改状态
Text("点击了 $count 次") // 读取状态 → count 变化时此处重组
}
}

3. 智能重组(Intelligent Recomposition)

Compose 编译器会跳过参数未变化的 Composable:

@Composable
fun UserProfile(name: String, age: Int) {
NameSection(name) // name 没变就跳过
AgeSection(age) // age 变了才重组
StaticSection() // 无参数,永远跳过
}

跳过的条件是参数具有稳定性(Stability)

  • 基本类型(Int、String、Boolean 等)→ 天然稳定
  • data class(所有属性都是 val 且稳定类型)→ 稳定
  • 包含 var 属性或 List(非 SnapshotStateList)→ 不稳定
// ✅ 稳定 - 所有属性都是 val + 不可变类型
data class User(val name: String, val age: Int)

// ❌ 不稳定 - 包含 List(被认为可变)
data class UserList(val users: List<User>)

// ✅ 添加 @Immutable 注解标记为稳定
@Immutable
data class UserList(val users: List<User>)

4. @Composable 编译原理

Compose 编译器插件会对 @Composable 函数做以下处理:

  1. 插入 Composer 参数:追踪 Composition 树
  2. 插入记忆化代码:缓存计算结果
  3. 插入比较逻辑:判断参数是否变化以决定是否跳过
  4. 生成 Group 标记:标识 Composable 在树中的位置

常见面试问题

Q1: Composition 和 Recomposition 有什么区别?

答案

  • Composition(初始组合):首次运行 Composable 函数,构建 UI 树。类似 View 体系的 inflate + onCreate
  • Recomposition(重组):状态变化后重新执行相关 Composable 函数,更新 UI 树中变化的部分

Compose 运行时通过差异比较只更新变化的节点,类似 React 的 Virtual DOM diff。

Q2: 为什么 Composable 函数不能有副作用?

答案

因为 Composable 函数可能被多次、乱序、并行执行。如果包含副作用(如网络请求),可能导致:

  • 重组时重复发起请求
  • 执行顺序不可预测
  • 被跳过时副作用丢失

副作用应该放在专用的 Effect API 中(如 LaunchedEffect)。

Q3: remember 的作用是什么?不用 remember 会怎样?

答案

remember 在 Composition 中缓存一个值,在重组时保持该值不变:

// ✅ 正确:remember 保持 state 跨重组存活
var count by remember { mutableStateOf(0) }

// ❌ 错误:每次重组都会创建新的 state,count 永远是 0
var count by mutableStateOf(0)

remember 的生命周期与所在 Composable 在 Composition 树中的位置绑定。当 Composable 从树中移除时,remember 的值也会被清除。

Q4: key 在 Compose 中有什么作用?

答案

key 用于帮助 Compose 识别列表中的元素身份,类似 React 的 key prop:

LazyColumn {
items(users, key = { it.id }) { user ->
UserItem(user)
}
}

没有 key 时,Compose 按索引匹配。如果列表中间插入元素,后续所有 Item 都会重组。有 key 后,Compose 可以精确追踪每个 Item,只重组真正变化的。

Q5: @Stable@Immutable 有什么区别?

答案

  • @Immutable:标记类的所有属性在构造后永远不会变化。这是更强的保证
  • @Stable:标记类满足以下条件:
    • 对同一实例调用 equals 的结果永远一致
    • 公共属性变化时会通知 Composition(如使用 mutableStateOf

两者都告诉 Compose 编译器可以安全地跳过重组。@Immutable@Stable 约束更严格。

相关链接