为何无状态Composable因传入函数触发重组?如何避免?
Jetpack Compose 无状态参数Composable不必要重组问题分析与解决
点击第一个MyBox调用changeParameter更新uiState后,第二个传入myViewModel::changeOtherParameter的MyBox会触发重组,但传入空Lambda的MyBox和显示myUiState.otherParameter的Text却不重组。以下是复现代码:
class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MyScetchApplicationTheme { Screen(MyViewModel()) } } } } @Composable fun Screen(myViewModel: MyViewModel) { val myUiState by myViewModel.uiState.collectAsState() Column { MyBox(myViewModel::changeParameter) // 点击此组件 MyBox(myViewModel::changeOtherParameter) // 此组件会重组 MyBox{ } // 但此组件不会 Text("${myUiState.otherParameter}") // 该Text也不会 } } @Composable private fun MyBox(function: () -> Unit) { Box( modifier = Modifier .padding(20.dp) .size(80.dp) .background(Color.Gray) .clickable { function() } ) } class MyViewModel: ViewModel() { private var _uiState = MutableStateFlow(MyUiState()) val uiState: StateFlow<MyUiState> = _uiState.asStateFlow() fun changeParameter() { _uiState.update { it.copy(parameter = !uiState.value.parameter) } } fun changeOtherParameter() { _uiState.update { it.copy(otherParameter = !uiState.value.otherParameter) } } } data class MyUiState ( val parameter: Boolean = false, val otherParameter: Boolean = false, )
现象原因
- Compose重组的核心规则:只有当Composable依赖的状态或参数发生变化时,才会触发重组。这里
Screen因为myUiState.parameter更新而重组,进而重新执行所有子Composable的调用逻辑。 - 函数参数的稳定性识别问题:
- 第二个
MyBox传入的myViewModel::changeOtherParameter:Compose默认没法识别这类成员函数引用的稳定性——因为() -> Unit这种函数类型没被标记为@Stable。哪怕这个函数引用本身根本没变化,Compose也会默认认为参数可能更新,从而触发MyBox重组。 - 第三个
MyBox的空Lambda:这个Lambda没捕获任何外部变量,Compose内部会把它优化成单例实例,认为它是稳定的,所以不会触发重组。 - 显示
otherParameter的Text:它依赖的myUiState.otherParameter根本没变化,符合Compose精准重组的规则,自然不会重组。
- 第二个
避免不必要重组的方案(保留函数参数)
方案1:用remember缓存函数引用
用remember把函数引用存起来,让Compose每次重组时都用同一个实例,这样参数就不会被判定为“变化”,也就不会触发MyBox重组:
@Composable fun Screen(myViewModel: MyViewModel) { val myUiState by myViewModel.uiState.collectAsState() // 缓存函数引用,确保每次重组都用同一个实例 val changeParamFunc = remember { myViewModel::changeParameter } val changeOtherParamFunc = remember { myViewModel::changeOtherParameter } Column { MyBox(changeParamFunc) MyBox(changeOtherParamFunc) MyBox{ } Text("${myUiState.otherParameter}") } }
方案2:用key函数锁定重组条件
把MyBox用key包裹,用稳定的myViewModel作为key。只要key不变,哪怕父Composable重组,MyBox也不会跟着重组:
@Composable fun Screen(myViewModel: MyViewModel) { val myUiState by myViewModel.uiState.collectAsState() Column { key(myViewModel) { MyBox(myViewModel::changeParameter) } key(myViewModel) { MyBox(myViewModel::changeOtherParameter) } MyBox{ } Text("${myUiState.otherParameter}") } }
方案3:封装稳定的函数持有者
自定义一个标记为@Stable的类来装函数,让Compose能识别它的稳定性,从而正确判断参数是否变化:
@Stable class ActionHolder(val action: () -> Unit) @Composable fun Screen(myViewModel: MyViewModel) { val myUiState by myViewModel.uiState.collectAsState() // 用稳定的Holder包裹函数,确保Compose能识别参数稳定性 val action1 = remember { ActionHolder(myViewModel::changeParameter) } val action2 = remember { ActionHolder(myViewModel::changeOtherParameter) } Column { MyBox(action1.action) MyBox(action2.action) MyBox{ } Text("${myUiState.otherParameter}") } }
内容的提问来源于stack exchange,提问作者Pan Wiewiorka
相关产品推荐
相关产品推荐

