撰写:为什么用 "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 可组合项。

我希望我们现在清楚了。