为什么在回调中调用 ViewModel 时会发生重组?

Why recomposition happens when call ViewModel in a callback?

我对compose的概念完全一头雾水。 我有一个代码

@Composable
fun HomeScreen(viewModel: HomeViewModel = getViewModel()) {
    Scaffold {
        val isTimeEnable by viewModel.isTimerEnable.observeAsState()
        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!!) {
                viewModel.setTime(it)
            }
        }
    }
}

@Composable
fun Clock(date: Long, selectTime: (date: Date) -> Unit) {
    NumberClock(Date(date)) {
        val time = SimpleDateFormat("HH:mm", Locale.ROOT).format(it)
        Timber.d("Selected time: time")
        selectTime(it)
    }
}

为什么 Clock 小部件在我点击开关时重新组合。如果我从 Clock 中删除行 selectTime(it),则不会发生小部件回调重组。 撰写版本:1.0.2

这是因为在 compose 方面,你每次都在创建一个新的 selectTime lambda,所以重新组合是必要的。如果你传递 setTime 函数作为引用,compose 会知道它是同一个函数,所以不需要重新组合:

Clock(viewModel.timeSelected.value!!, viewModel::setTime)

或者,如果您有更复杂的处理程序,您可以 remember 它。双括号 ({{ }}) 在这里很重要,因为你需要记住 lambda。

Clock(
    date = viewModel.timeSelected.value!!,
    selectTime = remember(viewModel) {
        {
            viewModel.setTimerEnable(it)
        }
    }
)

我知道它看起来有点奇怪,您可以使用 rememberLambda 这将使您的代码更具可读性:

selectTime = rememberLambda(viewModel) {
    viewModel.setTimerEnable(it)
}

请注意,您需要将所有可能更改的值作为键传递,因此 remember 将按需重新计算。


总的来说,重组并不是一件坏事。当然,如果你可以减少它,你应该这样做,但你的代码应该可以正常工作,即使它被重新组合了很多次。例如,您不应在可组合项内进行大量计算来执行此操作,而应使用 side effects.

因此,如果重组 Clock 会导致奇怪的 UI 效果,那么您的 NumberClock 可能有问题,无法在重组后存活下来。如果是这样,请将 NumberClock 代码添加到您的问题中,以获得有关如何改进它的建议。

这是预期的行为。当用户切换开关(通过调用 vm.setTimeenabled)时,您显然正在修改视图模型中的 isTimeEnabled 字段。现在,您的视图模型中的 isTimeEnabled 显然是一个 LiveData 实例,并且您通过调用 observeAsState 从 Composable 中引用该实例。因此,当您修改开关的 onValueChange 的值时,您实际上是在修改可组合项所依赖的状态。因此,为了呈现更新后的状态,会触发重组