Jetpack Compose回调中调用ViewModel为什么会触发组件重组?
Jetpack Compose 组件不必要重组问题解答
核心原因概述
Jetpack Compose的重组触发规则是:仅当可组合函数的输入参数发生变更或函数内部捕获的可观察状态发生变更时,才会触发重组。你遇到的现象完全符合Compose 1.0.2版本的参数稳定性判定规则。
点击Switch触发Clock重组的原因
- 触发源:点击Switch时修改了
viewModel.isTimerEnable的状态值,HomeScreen作为该状态的订阅方会首先触发重组。 - 入参稳定性判定:
Clock组件有两个入参:date: Long、selectTime: (date: Date) -> Unit。- 其中
Long是Compose默认认可的稳定不可变类型,只要viewModel.timeSelected.value没有变化,该入参不会触发重组。 - 函数类型的
selectTime参数属于不稳定类型:你传入的lambda捕获了未加@Stable注解的HomeViewModel实例(Compose默认将自定义ViewModel判定为不稳定类型),因此每次HomeScreen重组时都会生成全新的lambda实例。
- 最终
Clock的入参和上一次重组时不一致,因此触发了不必要的重组。
删除selectTime(it)后重组消失的原因
当你删除selectTime(it)代码行后,传入Clock的lambda不再捕获任何外部不稳定实例,也没有引用任何外部可变状态,属于无捕获的静态lambda。
Compose编译器会对这类lambda做全局单例优化,不会每次重组都创建新实例,此时Clock的两个入参都和上一次重组时完全一致,因此不会触发重组。
修复方案
你可以通过remember缓存回调lambda,避免每次重组生成新实例即可解决问题:
@Composable fun HomeScreen(viewModel: HomeViewModel = getViewModel()) { Scaffold { val isTimeEnable by viewModel.isTimerEnable.observeAsState() // 缓存selectTime回调,仅当viewModel变更时才重新生成 val onSelectTime = remember(viewModel) { { date: Date -> viewModel.setTime(date) } } Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, modifier = Modifier .fillMaxSize() .background(Color.Black), ) { Switch( checked = isTimeEnable ?: false, onCheckedChange = { viewModel.setTimerEnable(it) }, ) Clock(viewModel.timeSelected.value!!, onSelectTime) } } }
内容的提问来源于stack exchange,提问作者Vadim Zhukov
相关产品推荐
相关产品推荐

