我在 Jetpack Compose 中使用
LaunchedEffect(key)
时偶然发现了一个奇怪的问题,并将其追溯到以下最小示例:
Surface(
modifier = Modifier.fillMaxSize().safeDrawingPadding(),
color = MaterialTheme.colorScheme.background
) {
var pseudoState by remember {
mutableStateOf(false)
}
LaunchedEffect(Unit) {} // NOTE: I am using Unit here
Column {
Button(onClick = { pseudoState = !pseudoState }) {
Text(text = "TOGGLE pseudoState to ${!pseudoState}")
}
Text(text = "Random: ${Math.random()}")
}
}
当我运行它时,请注意,单击后
Button
已正确重新组合,并且会跳过其他 Text
可组合项。
现在,我做了一个小调整,将
pseudoState
作为 key
提供给空的 LaunchedEffect
:
LaunchedEffect(pseudoState) {} // NOTE: I am using pseudoState now
现在,每单击一次
Button
,Button
和 Text
都会重新组合:
为什么会出现这种情况?
我使用以下依赖项:
[versions]
agp = "8.3.2"
kotlin = "1.9.0"
coreKtx = "1.15.0"
lifecycleRuntimeKtx = "2.8.7"
activityCompose = "1.9.3"
composeBom = "2024.11.00"
虽然像
Math.random()
这样的函数不能触发重组,但它可以导致重组,如果:
正如文档所说:
每个可组合函数和 lambda 都可以自行重组。
这意味着当您使用
pseudoState
更改 LaunchedEffect(Unit)
值时,Compose 没有理由触摸除 Button
函数范围之外的任何内容,因为那是唯一 pseudoState
消费者所在的位置。如果你在那里添加一个随机的Text
,它将被重新组合:
var pseudoState by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {}
Column {
Button(onClick = {
pseudoState = !pseudoState
}) {
Column {
Text(text = "TOGGLE pseudoState to ${!pseudoState}")
Text(text = "Random:: ${Math.random()}") // recomposed on click
}
}
Text
被重构,因为当 Compose 评估其输入时,Math.random()
被调用并且 text
发生变化。例如,如果我们添加这样的函数:
private fun getSameNumber(): Int {
println("getSameNumber ${System.currentTimeMillis()}")
return 7
}
并将
Math.random()
替换为 getSameNumber()
,我们会看到每次点击都会调用 getSameNumber
,但 text
不会改变,并且 Button
不会被重组。
当您更改为
LaunchedEffect(pseudoState)
时,基本上会发生相同的情况,但在 Surface
的范围内。 Column
是一个 inline
函数,不会创建作用域。您可以使用其他一些可组合项创建嵌套范围,例如另一个 Button
:
Surface(
modifier = Modifier.fillMaxSize().safeDrawingPadding(),
color = MaterialTheme.colorScheme.background
) {
var pseudoState by remember { mutableStateOf(false) }
LaunchedEffect(pseudoState) {}
Column {
Button(onClick = {
pseudoState = !pseudoState
}) {
Column {
Text(text = "TOGGLE pseudoState to ${!pseudoState}")
}
}
Text(text = "Random: ${Math.random()}")
Text(text = "Same: ${getSameNumber()}") // Same input, doesn't recompose
Button(onClick = {}) {
Text(text = "Random: ${Math.random()}") // Another scope, doesn't recompose
}
}
}