用作输入参数的 Lambda 函数会导致重组

Ali*_*aei 7 android android-jetpack-compose compose-recomposition

考虑下面的片段

fun doSomething(){
}

@Composable
fun A() {
    Column() {
        val counter = remember { mutableStateOf(0) }
        B {
            doSomething()
        }
        Button(onClick = { counter.value += 1 }) {
            Text("Click me 2")
        }
        Text(text = "count: ${counter.value}")
    }
}

@Composable
fun B(onClick: () -> Unit) {
    Button(onClick = onClick) {
        Text("click me")
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,当按下“click me 2”按钮时,B compose 函数将被重新组合,尽管其中的任何内容都没有改变。

澄清: doSomething 用于演示目的。如果您坚持要一个实际的例子,您可以考虑以下 B 的用法:

B{
    coroutinScope.launch{
        bottomSheetState.collapse()
        doSomething()
    }
}
Run Code Online (Sandbox Code Playgroud)

我的问题:

  1. 为什么这个 lamda 函数会导致重组
  2. 修复它的最佳方法

我对这个问题的理解

从 compose 编译器报告中我可以看到 B 是一个可跳过的函数,并且输入 onClick 是稳定的。起初我认为这是因为 lambda 函数是在 A 的每次重组时重新创建的,并且它与之前的不同。这种差异会导致 B 的重组。但这不是真的,因为如果我在 lambda 函数内使用其他内容(例如更改状态),则不会导致重组。

我的解决方案

  1. 如果可能的话使用代表。就像 viewmode::doSomething 或 ::doSomething 一样。不幸的是,这并不总是可能的。
  2. 记住里面使用 lambda 函数:
val action = remember{
    {
        doSomething()
    }
}
B(action)
Run Code Online (Sandbox Code Playgroud)

看起来很难看 =) 3. 以上的组合。

Ali*_*aei 3

一般来说,如果您在 lambda 函数中使用不稳定的属性,则会导致子 compose 函数不可跳过,因此每次其父函数重组时都会被重组。这不是一件容易看到的事情,你需要小心对待。例如,下面的代码将导致 B 被重组,因为 coroutinScope 是一个不稳定的属性,我们将它用作 lambda 函数的间接输入。

fun A(){
    ...
    val coroutinScope = rememberCoroutineScope()
    B{
        coroutineScope.launch {
            doSomething()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

要绕过这个,您需要在 lambda 或委托(::运算符)周围使用记住。该视频中有一个关于它的注释。40:05左右

还有许多其他参数不稳定,例如上下文。要弄清楚它们,您需要使用撰写编译器报告。

这是关于原因的一个很好的解释: https: //multithreaded.stitchfix.com/blog/2022/08/05/jetpack-compose-recomposition/