我可能在这里遗漏了一些重要的可组合重组概念,但让我解释一下我的疑问。
我正在阅读有关 Android Compose 中的 副作用 的内容,我发现,只有当父可组合函数被重构时,才执行 SideEffect 可组合函数。这意味着,每次重构父可组合函数时,都会执行 SideEffect 的任何效果。现在,重组发生在以下情况:
因此,如果我们有一个名为 Counter1 的简单计数器可组合函数,则当该函数首次启动时,其中的 SideEffect 将被执行,此后,每次用户增加计数时(每次用户单击按钮时) ):
@Composable
fun Counter1() {
var counter: Int by remember { mutableStateOf(0) }
SideEffect {
Log.d("Test tag", "Counter1: $counter")
}
Column {
Button(onClick = { counter++ }) {
Text(text = "Increase count")
}
Text(text = "Counter value is: $counter")
}
}
如果我们稍微改变一下计数器,我们就会产生另一个计数器:
@Composable
fun Counter2() {
var counter: Int by remember { mutableStateOf(0) }
SideEffect {
Log.d("Test tag", "Counter2: $counter")
}
Column {
Button(onClick = { counter++ }) {
Text(text = "Increase count is: $counter")
}
}
}
第二个场景中的 SideEffect 将仅在第一次重组可组合项时执行。当用户改变状态(通过增加计数器)时它不会被执行。为什么会发生这种情况?我在 Counter2 中的第一行放置了一个断点,每次用户单击按钮时,计数器值都会增加,并且以新状态重新调用 Counter2。
为了扩展@Thracian的评论,
SideEffect
解释为重组仅限于SideEffect
所在的撰写树部分。是的,每次计数器增加时都会进行重组,但并不是所有都被重组,只是受影响的可组合项。
虽然
Counter2中的
counter
状态变量与副作用处于相同的组合范围内,但在该范围内的任何地方都没有使用它,因此不需要重新组合(并且会跳过副作用)。它在 Button
可组合项中用于调用 Text
,但这是一个单独的组合函数,有自己的作用域。
有趣的部分是为什么 Counter1 的行为不同。现在
counter
仅在 Column
可组合项中用于调用 Text
,但 Column
也是另一个可组合函数,应该有自己的作用域,因此 should 的行为相同(但事实并非如此) .
这就是 @Thracian 的评论发挥作用的地方:
Column
不仅仅是一个撰写函数,它还被声明为一个inline
函数。这意味着编译器通过用函数体的副本替换函数的每次调用来内联函数,然后删除该函数。在编译后的代码中,没有调用另一个 compose 函数,因此不会创建新的 compose 作用域,并且 Column
的内容改为使用周围的作用域。这恰好与 SideEffect
所在的位置相同。
这就是为什么在
Counter1中更改
counter
会触发包含 SideEffect
的范围的重组,因为它与包含 Text
(访问 counter
)的范围相同。