更新StateFlow状态时 未依赖变化值的CCompose为何仍触发重组
问题背景
本应用实现交互逻辑为:按下C组件时对A组件展示的计数值进行累加。由于仅有A组件依赖的计数值发生变化,预期仅A组件会触发重组,但实际运行时CCompose也发生了重组。

相关实现代码
ViewModel层(对外暴露StateFlow)
class MainViewModel : ViewModel() { private val _count: MutableStateFlow<Int> = MutableStateFlow(0) val count: StateFlow<Int> = _count.asStateFlow() fun increaseCount() { _count.value++ } }
可组合项实现
@Composable fun CountUpScreen( modifier: Modifier = Modifier, viewModel: MainViewModel = viewModel(), ) { val count: Int by viewModel.count.collectAsState() SideEffect { println("CountUpScreen") } Column( modifier = modifier.fillMaxSize(), verticalArrangement = Arrangement.SpaceEvenly, horizontalAlignment = Alignment.CenterHorizontally, ) { ACompose( count = count ) BCompose() CCompose { viewModel.increaseCount() } } } @Composable private fun ACompose(count: Int) { SideEffect { println("ACompose") } Column(modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) { Text( text = "$count" ) } } @Composable private fun BCompose() { SideEffect { println("BCompose") } Text( text = "I am composable that will not be recompose" ) } @Composable private fun CCompose(onClick: () -> Unit) { SideEffect { println("CCompose") } Button(onClick = { onClick() }) { Icon(Icons.Outlined.Add, contentDescription = "+") } }
运行日志
点击按钮累加计数时,日志输出如下:
I/System.out: CountUpScreen I/System.out: ACompose I/System.out: CCompose
原因说明
CCompose发生重组的核心原因是:父组件CountUpScreen重组时,每次都会为CCompose传入全新创建的onClick Lambda实例,入参引用发生变化,Compose判定参数不相等,因此触发CCompose重组。
具体逻辑拆解:
- CountUpScreen中通过
collectAsState()订阅了count的状态,count值变化时,CountUpScreen会首先被标记为待重组状态,进入重组流程。 - Compose重组父组件时,会逐个子组件校验入参的相等性,决定子组件是否需要跳过重组:
- ACompose的入参count值发生了实际变化,触发重组,符合预期。
- BCompose没有任何入参,不存在参数变更,直接跳过重组,因此日志中没有BCompose的输出。
- CCompose的入参是直接在调用位置写的Lambda表达式
{ viewModel.increaseCount() },这个Lambda没有做缓存处理,每次CountUpScreen重组执行到CCompose调用代码时,都会生成一个新的Lambda对象。虽然新旧Lambda的执行逻辑完全一致,但二者是不同的对象实例,引用不相等,Compose的参数校验不通过,就会触发CCompose重组。
验证方法:将传入CCompose的Lambda用
remember包裹做实例缓存,例如在CountUpScreen中定义val cOnClick = remember { { viewModel.increaseCount() } },再将cOnClick传给CCompose,后续count变化时CCompose就不会再被触发重组。
内容的提问来源于stack exchange,提问作者ko2ic
相关产品推荐
相关产品推荐

