为何带key的LaunchedEffect会触发整个Composable层级重组?
问题复现
初始代码与现象
以下是简化后的最小示例代码:
Surface( modifier = Modifier.fillMaxSize().safeDrawingPadding(), color = MaterialTheme.colorScheme.background ) { var pseudoState by remember { mutableStateOf(false) } LaunchedEffect(Unit) {} // 注意:此处使用Unit作为key Column { Button(onClick = { pseudoState = !pseudoState }) { Text(text = "TOGGLE pseudoState to ${!pseudoState}") } Text(text = "Random: ${Math.random()}") } }
运行后点击按钮,只有Button组件会正确重组(显示的文本更新),而下方的Text("Random: ${Math.random()}")不会重组,随机数保持不变。

修改后的代码与现象
仅将LaunchedEffect的key改为pseudoState:
LaunchedEffect(pseudoState) {} // 注意:此处使用pseudoState作为key
此时每次点击按钮,Button和下方的Text组件都会重组,随机数每次都会更新。

原因分析
这是由Jetpack Compose的状态追踪与最小重组机制决定的:
初始情况(LaunchedEffect(Unit))
LaunchedEffect的key为Unit,它不会读取pseudoState的值。Compose只会追踪在Composable执行期间被读取的状态,因此整个Surface的content lambda并不依赖pseudoState。当pseudoState变化时,只有直接读取了该状态的Button内部Text会触发重组,而未读取任何变化状态的下方Text则会被跳过,随机数不会重新计算。修改后情况(LaunchedEffect(pseudoState))
当把pseudoState作为LaunchedEffect的key时,Compose在执行Surface的content lambda时,会读取pseudoState的值来判断LaunchedEffect是否需要重启。这意味着整个content lambda现在依赖于pseudoState状态。当pseudoState变化时,整个content lambda会被触发重组,其中的所有子组件(包括未直接读取状态的Text)都会重新执行,因此Math.random()会重新生成,表现为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"
内容的提问来源于stack exchange,提问作者BenjyTec

