重组基本概念
重组(Recomposition)是Compose更新UI的核心机制,当状态发生变化时,Compose会重新执行相关的Composable函数来更新UI。
触发重组的情况
1. State状态变化
1.1 MutableState变化
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
// ✅ count变化时触发重组
Text("Count: $count")
Button(onClick = { count++ }) {
Text("Increment")
}
}
1.2 StateFlow/LiveData变化
@Composable
fun UserProfile(viewModel: UserViewModel) {
// ✅ collectAsState()监听状态变化
val user by viewModel.userState.collectAsState()
Text("Name: ${user.name}") // user变化时重组
}
1.3 derivedStateOf变化
@Composable
fun FilteredList(items: List<String>, query: String) {
// ✅ 当items或query变化时,filteredItems会重新计算
val filteredItems by remember(items, query) {
derivedStateOf {
items.filter { it.contains(query, ignoreCase = true) }
}
}
LazyColumn {
items(filteredItems) { item ->
Text(item)
}
}
}
2. 参数变化
2.1 直接参数变化
@Composable
fun Greeting(name: String) { // ✅ name变化时重组
Text("Hello, $name!")
}
@Composable
fun Parent() {
var name by remember { mutableStateOf("World") }
Greeting(name = name) // name变化导致Greeting重组
}
2.2 Lambda参数变化
@Composable
fun Parent() {
var count by remember { mutableStateOf(0) }
// ❌ 每次重组都创建新的lambda,导致Child重组
Child(onClick = { count++ })
// ✅ 使用remember缓存lambda
val onClick = remember { { count++ } }
Child(onClick = onClick)
}
3. 父组件重组导致的级联重组
@Composable
fun Parent() {
var parentState by remember { mutableStateOf(0) }
Column {
Text("Parent: $parentState") // 状态变化
// ❌ 父组件重组时,所有子组件默认也会重组
Child1()
Child2()
Child3()
}
}
// ✅ 解决方案:使用稳定的参数或记忆化
@Composable
fun OptimizedParent() {
var parentState by remember { mutableStateOf(0) }
Column {
Text("Parent: $parentState")
// ✅ 使用remember避免重组
remember { Child1() }
Child2() // 如果Child2没有参数且内容稳定,可能会跳过重组
}
}
4. 不稳定参数导致的重组
4.1 不稳定的数据类
// ❌ 不稳定的类
class UnstableData(var value: String)
@Composable
fun UnstableComponent(data: UnstableData) {
// 即使data.value没变,也可能重组
Text(data.value)
}
// ✅ 稳定的数据类
@Stable
data class StableData(val value: String)
// 或使用@Immutable
@Immutable
data class ImmutableData(val value: String)
4.2 函数类型参数
@Composable
fun Parent() {
var state by remember { mutableStateOf(0) }
// ❌ 每次重组创建新函数
Child { state++ }
// ✅ 缓存函数引用
val callback = remember(state) { { state++ } }
Child(callback)
}
5. CompositionLocal变化
val LocalTheme = compositionLocalOf { LightTheme }
@Composable
fun App() {
var isDark by remember { mutableStateOf(false) }
CompositionLocalProvider(
LocalTheme provides if (isDark) DarkTheme else LightTheme
) {
// ✅ LocalTheme变化时,所有使用它的组件都会重组
ThemedContent()
}
}
@Composable
fun ThemedContent() {
val theme = LocalTheme.current // 监听主题变化
Box(backgroundColor = theme.backgroundColor) {
Text("Themed content")
}
}
6. Side Effects触发的重组
6.1 LaunchedEffect
@Composable
fun TimerComponent() {
var time by remember { mutableStateOf(0) }
// ✅ 定时器更新state,触发重组
LaunchedEffect(Unit) {
while (true) {
delay(1000)
time++
}
}
Text("Time: $time")
}
6.2 DisposableEffect
@Composable
fun LocationComponent() {
var location by remember { mutableStateOf<Location?>(null) }
DisposableEffect(Unit) {
val listener = LocationListener { newLocation ->
location = newLocation // ✅ 触发重组
}
onDispose { /* cleanup */ }
}
Text("Location: ${location?.toString() ?: "Unknown"}")
}
重组优化策略
1. 使用稳定的参数
// ✅ 使用不可变数据
@Immutable
data class UserInfo(val name: String, val age: Int)
@Composable
fun UserCard(user: UserInfo) {
// user是稳定的,只有实际内容变化才重组
}
2. 合理使用remember
@Composable
fun ExpensiveComponent(data: List<String>) {
// ✅ 缓存复杂计算结果
val processedData = remember(data) {
data.map { it.uppercase() }.sortedBy { it.length }
}
LazyColumn {
items(processedData) { item ->
Text(item)
}
}
}
3. 状态提升和局部化
// ❌ 状态在顶层,导致整个组件树重组
@Composable
fun BadExample() {
var searchQuery by remember { mutableStateOf("") }
var isLoading by remember { mutableStateOf(false) }
Column {
SearchBar(query = searchQuery, onQueryChange = { searchQuery = it })
LoadingIndicator(isLoading = isLoading)
ContentList(query = searchQuery)
}
}
// ✅ 状态局部化
@Composable
fun GoodExample() {
Column {
SearchSection() // 搜索状态只影响这个区域
LoadingSection() // 加载状态只影响这个区域
ContentSection() // 内容状态只影响这个区域
}
}
4. 使用key()优化列表
@Composable
fun ItemList(items: List<Item>) {
LazyColumn {
items(
items = items,
key = { item -> item.id } // ✅ 提供稳定的key
) { item ->
ItemRow(item)
}
}
}
5. 避免在Composable中创建对象
@Composable
fun BadComponent() {
// ❌ 每次重组都创建新对象
val padding = PaddingValues(16.dp)
val shape = RoundedCornerShape(8.dp)
Box(modifier = Modifier.padding(padding))
}
@Composable
fun GoodComponent() {
// ✅ 在外部定义或使用remember
Box(modifier = Modifier.padding(16.dp))
}
// 或者
private val DefaultPadding = PaddingValues(16.dp)
private val DefaultShape = RoundedCornerShape(8.dp)
总结
重组触发的主要情况:
- State变化 - 最常见的触发原因
- 参数变化 - 包括直接参数和lambda参数
- 父组件重组 - 级联效应
- 不稳定参数 - 导致不必要的重组
- CompositionLocal变化 - 影响范围广
- Side Effects - 异步操作结果
优化关键点:
- 使用稳定的数据结构
- 合理使用remember缓存
- 状态局部化
- 避免在Composable中创建对象
- 提供稳定的key给列表项