简体   繁体   English

Lambda function 用作输入参数导致重组

[英]Lambda function used as input argument causes recomposition

Consider snippet below考虑下面的片段

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")
    }
}

Now when pressing "click me 2" button the B compose function will get recomposed although nothing inside it is got changed.现在当按下“点击我 2”按钮时,B compose function 将被重新组合,尽管它里面没有任何改变。

Clarification: doSomething is for demonstration purposes.说明 doSomething 用于演示目的。 If you insist on having a practical example you can consider below usage of B:如果你坚持要有一个实际的例子,你可以考虑下面 B 的用法:

B{
    coroutinScope.launch{
        bottomSheetState.collapse()
        doSomething()
    }
}

My questions:我的问题:

  1. Why this lamda function causes recomposition为什么这个 lamda function 导致重组
  2. Best ways to fix it修复它的最佳方法

My understanding of this problem我对这个问题的理解

From compose compiler report I can see B is an skippable function and the input onClick is stable.从 compose 编译器报告中我可以看到 B 是一个可跳过的 function 并且输入 onClick 是稳定的。 At first I though its because lambda function is recreated on every recomposition of A and it is different to previous one.起初我虽然是因为 lambda function 在每次重组 A 时都会重新创建,并且它与之前的不同。 And this difference cause recomposition of B. But it's not true because if I use something else inside the lambda function, like changing a state, it won't cause recomposition.而这种差异会导致B的重组。但这不是真的,因为如果我在lambda function内部使用其他东西,比如改变state,它不会导致重组。

My solutions我的解决方案

  1. use delegates if possible.尽可能使用委托。 Like viewmode::doSomething or::doSomething.像 viewmode::doSomething 或 ::doSomething。 Unfortunately its not always possible.不幸的是,它并不总是可能的。
  2. Use lambda function inside remember:使用 lambda function 里面记住:
val action = remember{
    {
        doSomething()
    }
}
B(action)

It seems ugly =) 3- Combinations of above.看起来很难看 =) 3- 以上的组合。

When you click the Button "Click me 2" the A composable is recomposed because of Text(text = "count: ${counter.value}") .当您点击Button “Click me 2”时, A可组合项会因为Text(text = "count: ${counter.value}")而重新组合。 It happens because it recompose the scope that are reading the values that can change.发生这种情况是因为它重组了正在读取可以更改的值的 scope。

If you are using something like:如果您使用的是:

B {
    Log.i("TAG","xxxx")
}

the B composable is NOT recomposed clicking the Button "Click me 2".单击Button “Click me 2”不会重新组合 B 可组合项。

If you are using如果您正在使用

B{
    coroutinScope.launch{
        Log.i("TAG","xxxx")
    }
}

the B composable is recomposed. B 可组合项被重组。

When a State is read it triggers recomposition in nearest scope. And a scope is a function that is not marked with inline and returns Unit.当读取 State 时,它会触发最近的 scope 中的重组。而 scope 是未标记内联并返回 Unit 的 function。
To use a coroutinScope you have to use rememberCoroutineScope that is a composable inline function. The the body of inline composable functions are simply copied into their call sites, such functions do not get their own recompose scopes.要使用coroutinScope ,您必须使用rememberCoroutineScope ,它是一个可组合的内联 function。 inline可组合函数的主体被简单地复制到它们的调用站点,这些函数没有自己的重组范围。

To avoid it you can use:为避免它,您可以使用:

B {
    Log.i("TAG","xxxx")
}

and

@Composable
fun B(onClick: () -> Unit) {
    val scope = rememberCoroutineScope()
   
    Button(
        onClick = {
            scope.launch {
                onClick()
            }
        }
    ) {
        Text(
            "click me ",
        )
    }
}

Sources and credits:来源和学分:

You can use the LogCompositions composable described in the 2nd post to check the recomposition in your code.您可以使用第 2 篇博文中描述的LogCompositions可组合项来检查代码中的重组。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM