考虑这个最小的代码片段(Kotlin 中):
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import java.time.LocalDateTime
import java.util.*
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
var time by remember {
mutableStateOf("time")
}
Column(modifier = Modifier.clickable { time = LocalDateTime.now().toString() }) {
Text(text = UUID.randomUUID().toString())
Text(text = time)
}
}
}
}
检查上面的代码,从逻辑角度来看,我们预计在单击
Column
时,由于只有参数 time
发生变化,因此只有较低时间 Text
可组合项会被重绘。这是因为 重组会尽可能跳过。
但是,人们发现上面的
Text
可组合项也被重绘(显示的 UUID 不断变化)。
请注意,我的
Column
可组合项的非幂等性应该没有影响,除非重画是愚蠢的。
您可以尝试运行这段代码
@Composable
fun IdempotenceTest() {
var time by remember {
mutableStateOf("time")
}
Column(
modifier = Modifier.clickable {
time = LocalDateTime.now().toString()
}
) {
Text(text = getRandomUuid())
TestComposable(text = returnSameValue())
Text(text = time)
}
}
@Composable
fun TestComposable(text: String) {
SideEffect {
Log.d(TAG, "TestComposable composed with: $text")
}
Text(text = text)
}
private fun getRandomUuid(): String {
Log.d(TAG, "getRandomUuid: called")
return UUID.randomUUID().toString()
}
private fun returnSameValue(): String {
Log.d(TAG, "returnSameValue: called")
return "test"
}
如果您检查日志,您会发现每次状态更改时,编译器都会尝试重新调用正在读取状态值的最小封闭 lamda/函数。因此, IdempotenceTest 函数(在我的例子中和你的例子中的 setContent{} lamda )将被重新执行,它将调用
getRandomUuid
和 returnSameValue
函数,并根据它们返回的值,它将决定是否重新组合依赖于这些返回值的元素。如果您想防止计算一次又一次地发生,请将其包装在 remember{}
块中。
现在,如果您使用
Button
代替 Column,您会发现只有相同的内容 lamda 才会被执行。发生这种情况的原因是 Column 是内联函数,而 Button 本身使用了内部的 Surface,这是非内联的。因此,Column{}
的内容被复制到封闭的功能块内,从而导致整个IdempotenceTest
对于重新组合无效。
顺便说一句,可组合函数必须无副作用,以确保幂等性。您可以在这里阅读更多内容。
rememberSavable {...}
为我工作。
它允许您直接从
clickable
更新状态并触发重组:
...
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
...
var time by rememberSaveable { mutableStateOf("time") }
Column(
modifier = Modifier.clickable {
time = LocalDateTime.now().toString()
}
) {
Text(text = UUID.randomUUID().toString())
Text(text = time)
}
这是因为 Modifier.clickable lambda 每次都会返回一个新实例。这将导致重组。只需将修饰符包装在记住块中即可。
val myModifier = remember {
Modifier.clickable { time = LocalDateTime.now().toString() }
}
Column(modifier = myModifier){
...
}