Jetpack Compose Scoped/Smart Recomposition全量重组原因求解
Jetpack Compose 智能重组机制实验说明
我为了理解重组(recomposition)和智能重组(smart recomposition)机制做了实验并编写了示例:
示例中的颜色通过Random.nextIn()生成,仅用于肉眼观测重组,颜色设置本身不影响重组逻辑,我也测试过未修改颜色的场景,现象一致。
动图内容分为三个部分:
Sample1
@Composable private fun Sample1() { Column( modifier = Modifier .background(getRandomColor()) .fillMaxWidth() .padding(4.dp) ) { var counter by remember { mutableStateOf(0) } Text("Sample1", color = getRandomColor()) Button( modifier = Modifier .fillMaxWidth() .padding(vertical = 4.dp), colors = ButtonDefaults.buttonColors(backgroundColor = getRandomColor()), onClick = { counter++ }) { Text("Counter: $counter", color = getRandomColor()) } } }
这部分智能重组表现符合预期,顶部Text未读取counter状态变化,因此仅Button内部的Text会触发重组。
Sample2
@Composable private fun Sample2() { Column( modifier = Modifier.background(getRandomColor()) ) { var update1 by remember { mutableStateOf(0) } var update2 by remember { mutableStateOf(0) } println("ROOT") Text("Sample2", color = getRandomColor()) Button( modifier = Modifier .padding(start = 8.dp, end = 8.dp, top = 4.dp) .fillMaxWidth(), colors = ButtonDefaults.buttonColors(backgroundColor = getRandomColor()), onClick = { update1++ }, shape = RoundedCornerShape(5.dp) ) { println("🔥 Button1️") Text( text = "Update1: $update1", textAlign = TextAlign.Center, color = getRandomColor() ) } Button( modifier = Modifier .padding(start = 8.dp, end = 8.dp, top = 2.dp) .fillMaxWidth(), colors = ButtonDefaults.buttonColors(backgroundColor = getRandomColor()), onClick = { update2++ }, shape = RoundedCornerShape(5.dp) ) { println("🍏 Button 2️") Text( text = "Update2: $update2", textAlign = TextAlign.Center, color = getRandomColor() ) } Column( modifier = Modifier.background(getRandomColor()) ) { println("🚀 Inner Column") var update3 by remember { mutableStateOf(0) } Button( modifier = Modifier .padding(start = 8.dp, end = 8.dp, top = 2.dp) .fillMaxWidth(), colors = ButtonDefaults.buttonColors(backgroundColor = getRandomColor()), onClick = { update3++ }, shape = RoundedCornerShape(5.dp) ) { println("✅ Button 3️") Text( text = "Update2: $update2, Update3: $update3", textAlign = TextAlign.Center, color = getRandomColor() ) } } Column() { println("☕️ Bottom Column") Text( text = "Sample2", textAlign = TextAlign.Center, color = getRandomColor() ) } } }
这部分表现也符合预期,每个mutableState仅会更新它被观测的作用域,只有同时读取update2和update3的Text会在这两个状态更新时触发重组。
Sample3
@Composable private fun Sample3() { Column( modifier = Modifier.background(getRandomColor()) ) { var update1 by remember { mutableStateOf(0) } var update2 by remember { mutableStateOf(0) } println("ROOT") Text("Sample3", color = getRandomColor()) Button( modifier = Modifier .padding(start = 8.dp, end = 8.dp, top = 4.dp) .fillMaxWidth(), colors = ButtonDefaults.buttonColors(backgroundColor = getRandomColor()), onClick = { update1++ }, shape = RoundedCornerShape(5.dp) ) { println("🔥 Button1️") Text( text = "Update1: $update1", textAlign = TextAlign.Center, color = getRandomColor() ) } Button( modifier = Modifier .padding(start = 8.dp, end = 8.dp, top = 2.dp) .fillMaxWidth(), colors = ButtonDefaults.buttonColors(backgroundColor = getRandomColor()), onClick = { update2++ }, shape = RoundedCornerShape(5.dp) ) { println("🍏 Button 2️") Text( text = "Update2: $update2", textAlign = TextAlign.Center, color = getRandomColor() ) } Column { println("🚀 Inner Column") var update3 by remember { mutableStateOf(0) } Button( modifier = Modifier .padding(start = 8.dp, end = 8.dp, top = 2.dp) .fillMaxWidth(), colors = ButtonDefaults.buttonColors(backgroundColor = getRandomColor()), onClick = { update3++ }, shape = RoundedCornerShape(5.dp) ) { println("✅ Button 3️") Text( text = "Update2: $update2, Update3: $update3", textAlign = TextAlign.Center, color = getRandomColor() ) } } // 🔥🔥 读取update1导致整个Composable触发重组 Column( modifier = Modifier.background(getRandomColor()) ) { println("☕️ Bottom Column") Text( text = "Update1: $update1", textAlign = TextAlign.Center, color = getRandomColor() ) } } }
Sample2和Sample3的唯一区别是Sample3底部的Text读取了update1状态,这一改动导致整个composable触发了全量重组,从动图中可以看到修改update1时Sample3的整体配色全部发生变化。
全量重组原因说明
该现象由Compose的重组作用域规则直接决定:
- Compose的重组作用域是读取状态的最近非内联Composable函数/匿名Lambda块,而
Column、Row、Box这类基础布局都是inline函数,本身不会成为独立的重组作用域。 - Sample3中
update1的读取位置在最外层Column的内部,外层Column是内联函数,因此读取update1的最近非内联作用域就是整个Sample3函数本身。 - 当
update1发生变化时,Compose会定位到它的读取作用域即整个Sample3触发重组,所有直接写在Sample3根作用域下的代码(比如根Column的background(getRandomColor())、println("ROOT")、顶部的Text("Sample3")等)都会被重新执行,因此会观测到全局配色变化。 - 对比Sample2,
update1的读取仅发生在第一个Button的content Lambda内部,该Lambda是独立的非内联作用域,所以状态变化仅会触发这个Lambda的重组,不会影响外层。
如果要避免全量重组,只要把读取update1的部分封装到一个独立的非内联Composable函数里即可,这样update1的读取作用域就被限制在这个新的子函数内部,状态变化不会触发外层Sample3全量重组。
内容的提问来源于stack exchange,提问作者Thracian
相关产品推荐
相关产品推荐

