Jetpack Compose-组件的渲染机制

前言

在 Jetpack Compose 中,组件(@Composable 函数)的重新渲染(即重组)是由其依赖的状态变化触发的。

重组触发条件

具体触发时机可以总结为以下几种情况:

依赖的状态发生变化

@Composable 函数直接或间接依赖的可变状态(如 mutableStateOfStateFlowLiveData 等)发生更新时,Compose 会触发该组件的重组。

示例

1
2
3
4
5
6
7
8
9
10
@Composable
fun Counter() {
// 定义可变状态
val count = remember { mutableStateOf(0) }

Button(onClick = { count.value++ }) {
// 依赖 count 状态,当 count 变化时,Text 会重组
Text("点击了 ${count.value} 次")
}
}

count.value 变化时,Text 组件会重新执行(重组)以展示最新值。

父组件重组且传入的参数发生变化

如果父组件发生重组,且传递给子组件的参数值与上次不同,则子组件会触发重组。反之,若参数未变化,子组件会被跳过(智能重组)。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Composable
fun Parent() {
val count = remember { mutableStateOf(0) }

// 父组件重组时,会向 Child 传递参数 count.value
Child(text = "计数: ${count.value}")

Button(onClick = { count.value++ }) {
Text("增加")
}
}

@Composable
fun Child(text: String) {
// 当 text 变化时,Child 会重组
Text(text)
}

count 变化导致父组件重组时,若 text 参数值改变,Child 会重组;若 text 不变(如参数是固定值),则 Child 不会重组。

组件被 key 标记且 key 值变化

使用 key 函数可以强制指定组件的标识。当 key 值变化时,即使参数未变,组件也会重新创建(而非复用),触发重组。

示例

1
2
3
4
5
6
LazyColumn {
items(users, key = { it.id }) { user ->
// 正确:使用唯一且稳定的 key(如用户 ID)
UserItem(user)
}
}

总结

Compose 的重组核心原则是“只重组依赖变化状态的组件”,通过这种精细化的更新机制,避免了不必要的渲染开销,提升了性能。开发者无需手动触发刷新,只需关注状态管理,Compose 会自动处理重组逻辑。

变化的场景

ViewModel中

若组件依赖 ViewModel 中的状态(如 StateFlow 暴露的数据),当数据更新并通过 collectAsState() 等方法被组件观察到时,会触发重组。

示例

1
2
3
4
5
6
7
8
9
10
11
12
@Composable
fun UserList(viewModel: UserViewModel) {
// 观察 ViewModel 中的 StateFlow
val users by viewModel.users.collectAsState()

// 当 users 变化时,LazyColumn 会重组
LazyColumn {
items(users) { user ->
Text(user.name)
}
}
}

Effect中

通过 LaunchedEffectDisposableEffect 等副作用 API 改变状态时,若组件依赖该状态,也会触发重组。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Composable
fun Timer() {
val time = remember { mutableStateOf(0) }

LaunchedEffect(Unit) {
// 副作用中更新状态,触发重组
while (true) {
delay(1000)
time.value++
}
}

Text("已运行: ${time.value} 秒")
}

没有触发重组解决

可变状态属性发生变化是不会重组的。

我们可以使用copy触发重组

1
currQues.value = currQues.value?.copy(isCorrect = isCorrect)

但是注意下面的写法不会触发重组

1
2
currQues.value?.isCorrect = isCorrect
currQues.value = currQues.value?.copy(isCorrect = isCorrect)

所以注意

需要属性修改的时候重组,就不能提前先赋值,赋值后就算copy了,也不会重组。