You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Composable Lambda为何未像非Composable Lambda一样被Memoize?

Compose编译器中Composable Lambda的Memoization疑问

我正在研究Compose编译器中Lambda的Memoization机制,聚焦于ComposerLambdaMemoization相关逻辑。根据我的理解:

  • 对于非Composable Lambda,如果它仅捕获稳定值,编译器会自动用remember调用包裹该Lambda,避免每次重组都创建新实例。
  • 但这种自动记忆化的处理仅针对非Composable Lambda。

对于Composable Lambda,Compose编译器会创建一个合成类ComposableLambdaImpl,并通过remember保存这个实例。但每次重组时,Kotlin仍会分配新的Lambda实例,随后调用ComposableLambdaImpl.update方法将新实例传入。由于不同Lambda实例永远不会相等,ComposableLambdaImpl.update总会导致调用该Composable Lambda的重组作用域失效。

为验证这个假设,我编写了以下测试代码:

class ComposableLambdaActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            val staticHeader = "Should composable lamdba be remembered?"
            val counter = remember { mutableIntStateOf(0) }
            val counterValue = counter.intValue
            LambdaMemoization(
                onClicked = { counter.intValue += 1 },
                header = {
                    Text(text = staticHeader)
                    Log.e("ComposableLambdaTest", "Header recomposed!")
                }
            ) {
                Text(text = "Clicked $counterValue times!")
            }
        }
    }
}

@Composable
fun LambdaMemoization(
    onClicked: () -> Unit,
    header: @Composable () -> Unit,
    body: @Composable () -> Unit
) {
    Log.e("ComposableLambdaTest", buildString {
        append("onClicked: ${onClicked}@${onClicked.hashCode()}\n")
        append("header: ${header}\n")
        append("body: ${body}\n")
    })
    Column {
        header()
        Button(onClick = { onClicked() }) {
            body()
        }
    }
}

多次点击按钮后,"Header recomposed!"日志被反复打印——尽管header Lambda仅捕获了一个静态字符串。我推测编译器生成的代码逻辑大致如下:

setContent {
    val staticHeader = "Should composable lamdba be remembered?"
    val counter = remember { mutableIntStateOf(0) }
    val counterValue = counter.intValue
    val nonComposableLambdaOnClick = remember(counter.intValue) {{ counter.intValue += 1 }}
    val composableLambdaHeader = {
        Text(text = staticHeader)
        Log.e("ComposableLambdaTest", "Header recomposed!")
    }
    val wrappedComposableLambdaHeader = remember {
        ComposableLambdaImpl(key, true, composableLambdaHeader)
    }
    wrappedComposableLambdaHeader.update(composableLambdaHeader)

    val composableLambdaBody = {
        Text(text = "Clicked $counterValue times!")
    }
    val wrappedComposableLambdaBody = remember {
        ComposableLambdaImpl(key, true, composableLambdaBody)
    }
    wrappedComposableLambdaBody.update(composableLambdaBody)
    LambdaMemoization(
        onClicked = nonComposableLambdaOnClick,
        header = wrappedComposableLambdaHeader,
        body = wrappedComposableLambdaBody
    )
}

可以看到composableLambdaHeader和composableLambdaBody并未被remember包裹,因此每次重组都会重新创建实例。我的疑问是:为什么Compose编译器不像处理非Composable Lambda那样,直接用remember包裹这些Composable Lambda?


内容的提问来源于stack exchange,提问作者darklord

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 14:46:31