撰写:为什么用 "remember" 启动的列表触发方式与快照不同
Compose: Why does a list initiated with "remember" trigger differently to Snapshot
我一直在研究 Jetpack Compose,目前正在研究 creating/managing/updating 状态的不同方式。
我引用的完整代码是 on my github
我用 3 种不同的方式制作了一份状态列表,并注意到行为上的差异。当第一个列表按钮被按下时,它会导致所有 3 个按钮被重新组合。当其他 2 个列表中的任何一个被单击时,尽管它们记录列表已更改大小,更新它们的 UI 但不会触发按钮的重组 ?
为了澄清我的问题,为什么当我按下 firsList 的按钮时,我收到以下日志消息以及大小更新:
绘制第一个DO列表按钮
绘图列表按钮
绘制第二个 DO 列表按钮
绘图列表按钮
绘制第三个 DO 列表按钮
绘图列表按钮
但是当我按下其他 2 个列表的按钮时,我只收到尺寸更新日志消息?
列表的大小现在是:2
列表大小现在为:2
var firstList by remember{mutableStateOf(listOf("a"))}
val secondList: SnapshotStateList<String> = remember{ mutableStateListOf("a") }
val thirdList: MutableList<String> = remember{mutableStateListOf("a")}
Row(...) {
println("Drawing first DO list button")
ListButton(list = firstList){
firstList = firstList.plus("b")
}
println("Drawing second DO list button")
ListButton(list = secondList){
secondList.add("b")
}
println("Drawing third DO list button")
ListButton(list = thirdList){
thirdList.add("b")
}
}
当我单击该按钮时,它会添加到列表中并显示一个值。我记录正在重新组合的内容以帮助查看正在发生的事情。
@Composable
fun ListButton(modifier: Modifier = Modifier,list: List<String>, add: () -> Unit) {
println("Drawing List button")
Button(...,
onClick = {
add()
println("Size of list is now: ${list.size}")
}) {
Column(...) {
Text(text = "List button !")
Text(text = AllAboutStateUtil.alphabet[list.size-1])
}
}
}
如果有人能指出我在正确的区域进行查看,我将不胜感激,以便我能够理解这一点。感谢您抽出宝贵时间。
我不是专家(好吧,),但这显然与相关列表的可变性有关。你看,Kotlin 对待可变和不可变列表的方式不同(ListOf<T>
不提供 add/delete 方法的原因),这意味着它们在功能上根本不同。
在第一种情况下,您使用的是不可变的 listOf()
,一旦创建就无法修改。因此,plus
必须在技术上创建一个新列表。
现在,由于您在父 Composable 的范围内声明不可变列表,因此当您对其调用 plus
时,将创建一个新列表,从而触发整个 Composable 中的重组。这是因为,如前所述,您正在读取父 Composable 范围内的变量,这使得 Compose 认为整个 Composable 需要反映该列表对象中的更改。因此,重组。
另一方面,您在其他两种方法中使用的列表类型是 SnapshotStateList<T>
,专为 Compose 中的列表操作而设计。现在,当你调用它的 add
或其他改变它的内容的方法时,并没有创建一个新的对象,而是发出了一个重组信号(这不是字面意思,只是你理解的一种方式)。重组工作的内部机制 SnapshotStateList<T>
旨在仅在实际 content-altering 操作发生时以及某些可组合项正在读取其内容时触发重组。因此,它触发重组的唯一地方是正在读取列表大小的列表按钮,用于记录目的。
简而言之,第一种方法会触发完整的重组,因为它使用了一个不可变的列表,该列表在修改后是 re-created,因此整个 Composable 都会收到它正在读取的内容已更改的通知。另一方面,其他两种方法使用“正确”类型的列表,使它们按预期运行,即仅通知其内容的直接读者,并且当内容(列表的元素)实际上变化。
清除了吗?
编辑:
EXPLANATION/CORRECTION 以下提出的理论:
您没有在代码中提到 MutableListDos
,但我猜它是您提供的代码的直接父级。所以,不,你的理论并不完全正确,因为不可变列表没有在 lambda 中读取(仅),但是你声明它的时刻和确切范围,你发送的消息是这个值正在被读取然后那里。因此,即使您删除了 lambda(并以某种方式从其他地方对其进行了修改),它仍会触发重组。 Row
仍然有一个 Composable
范围,即它能够很好地进行独立重组,但是变量本身在父 Composable 中声明(并因此读取),在范围之外Row
,它会导致对整个父级进行重新编译,而不仅仅是 Row
可组合项。
我希望我们现在清楚了。
我一直在研究 Jetpack Compose,目前正在研究 creating/managing/updating 状态的不同方式。
我引用的完整代码是 on my github
我用 3 种不同的方式制作了一份状态列表,并注意到行为上的差异。当第一个列表按钮被按下时,它会导致所有 3 个按钮被重新组合。当其他 2 个列表中的任何一个被单击时,尽管它们记录列表已更改大小,更新它们的 UI 但不会触发按钮的重组 ?
为了澄清我的问题,为什么当我按下 firsList 的按钮时,我收到以下日志消息以及大小更新:
绘制第一个DO列表按钮
绘图列表按钮
绘制第二个 DO 列表按钮
绘图列表按钮
绘制第三个 DO 列表按钮
绘图列表按钮
但是当我按下其他 2 个列表的按钮时,我只收到尺寸更新日志消息?
列表的大小现在是:2
列表大小现在为:2
var firstList by remember{mutableStateOf(listOf("a"))}
val secondList: SnapshotStateList<String> = remember{ mutableStateListOf("a") }
val thirdList: MutableList<String> = remember{mutableStateListOf("a")}
Row(...) {
println("Drawing first DO list button")
ListButton(list = firstList){
firstList = firstList.plus("b")
}
println("Drawing second DO list button")
ListButton(list = secondList){
secondList.add("b")
}
println("Drawing third DO list button")
ListButton(list = thirdList){
thirdList.add("b")
}
}
当我单击该按钮时,它会添加到列表中并显示一个值。我记录正在重新组合的内容以帮助查看正在发生的事情。
@Composable
fun ListButton(modifier: Modifier = Modifier,list: List<String>, add: () -> Unit) {
println("Drawing List button")
Button(...,
onClick = {
add()
println("Size of list is now: ${list.size}")
}) {
Column(...) {
Text(text = "List button !")
Text(text = AllAboutStateUtil.alphabet[list.size-1])
}
}
}
如果有人能指出我在正确的区域进行查看,我将不胜感激,以便我能够理解这一点。感谢您抽出宝贵时间。
我不是专家(好吧,),但这显然与相关列表的可变性有关。你看,Kotlin 对待可变和不可变列表的方式不同(ListOf<T>
不提供 add/delete 方法的原因),这意味着它们在功能上根本不同。
在第一种情况下,您使用的是不可变的 listOf()
,一旦创建就无法修改。因此,plus
必须在技术上创建一个新列表。
现在,由于您在父 Composable 的范围内声明不可变列表,因此当您对其调用 plus
时,将创建一个新列表,从而触发整个 Composable 中的重组。这是因为,如前所述,您正在读取父 Composable 范围内的变量,这使得 Compose 认为整个 Composable 需要反映该列表对象中的更改。因此,重组。
另一方面,您在其他两种方法中使用的列表类型是 SnapshotStateList<T>
,专为 Compose 中的列表操作而设计。现在,当你调用它的 add
或其他改变它的内容的方法时,并没有创建一个新的对象,而是发出了一个重组信号(这不是字面意思,只是你理解的一种方式)。重组工作的内部机制 SnapshotStateList<T>
旨在仅在实际 content-altering 操作发生时以及某些可组合项正在读取其内容时触发重组。因此,它触发重组的唯一地方是正在读取列表大小的列表按钮,用于记录目的。
简而言之,第一种方法会触发完整的重组,因为它使用了一个不可变的列表,该列表在修改后是 re-created,因此整个 Composable 都会收到它正在读取的内容已更改的通知。另一方面,其他两种方法使用“正确”类型的列表,使它们按预期运行,即仅通知其内容的直接读者,并且当内容(列表的元素)实际上变化。
清除了吗?
编辑:
EXPLANATION/CORRECTION 以下提出的理论:
您没有在代码中提到 MutableListDos
,但我猜它是您提供的代码的直接父级。所以,不,你的理论并不完全正确,因为不可变列表没有在 lambda 中读取(仅),但是你声明它的时刻和确切范围,你发送的消息是这个值正在被读取然后那里。因此,即使您删除了 lambda(并以某种方式从其他地方对其进行了修改),它仍会触发重组。 Row
仍然有一个 Composable
范围,即它能够很好地进行独立重组,但是变量本身在父 Composable 中声明(并因此读取),在范围之外Row
,它会导致对整个父级进行重新编译,而不仅仅是 Row
可组合项。
我希望我们现在清楚了。