【问题标题】:Why can't a variable wrapped with remember be assigned to new value in JetPack Compose?为什么不能在 JetPack Compose 中将用 remember 包裹的变量分配给新值?
【发布时间】:2022-12-15 10:48:28
【问题描述】:

我运行代码 A 并得到结果 A。

你会发现当系统调用Log.e("my", "Load $temp ${refresh.value}")var temp总是1,即使我一次又一次地点击按钮。

你知道 var temp 是用 remember 包装的,我在 Button 的 onClick 事件中为它分配了 2。

我的代码有什么问题?

顺便说一句,如果你运行代码 B,它会像我预期的那样得到结果 B!

代码A

var temp = remember { 1 }
val refresh = remember { mutableStateOf(100) }

Log.e("my", "Load $temp ${refresh.value}")

Button(
    onClick = {
        temp++
        refresh.value++

        Log.e("my", "Save $temp ${refresh.value}")
    }
) {
    Text("OK $temp ${refresh.value}")
}

结果A

2022-08-29 11:05:36.825 29337-29337/info.dodata.soundmeter E/my: Load 1 100
2022-08-29 11:05:37.550 29337-29337/info.dodata.soundmeter E/my: Load 1 100
2022-08-29 11:05:39.596 29337-29337/info.dodata.soundmeter E/my: Save 2 101
2022-08-29 11:05:39.600 29337-29337/info.dodata.soundmeter E/my: Load 1 101
2022-08-29 11:05:43.274 29337-29337/info.dodata.soundmeter E/my: Save 2 102
2022-08-29 11:05:43.278 29337-29337/info.dodata.soundmeter E/my: Load 1 102
2022-08-29 11:05:52.068 29337-29337/info.dodata.soundmeter E/my: Save 2 103
2022-08-29 11:05:52.071 29337-29337/info.dodata.soundmeter E/my: Load 1 103
2022-08-29 11:05:58.509 29337-29337/info.dodata.soundmeter E/my: Save 2 104
2022-08-29 11:05:58.511 29337-29337/info.dodata.soundmeter E/my: Load 1 104
   

代码B

    var temp = remember { 1 }
    val refresh = remember { mutableStateOf(100) }

    //Log.e("my", "Load $temp ${refresh.value}") //I remove it

    Button(
        onClick = {
            temp++
            refresh.value++

            Log.e("my", "Save $temp ${refresh.value}")
        }
    ) {
        Text("OK $temp ${refresh.value}")
    }

结果B

2022-08-29 11:13:30.624 31545-31545/info.dodata.soundmeter E/my: Save 2 101
2022-08-29 11:13:31.750 31545-31545/info.dodata.soundmeter E/my: Save 3 102
2022-08-29 11:13:33.003 31545-31545/info.dodata.soundmeter E/my: Save 4 103
2022-08-29 11:13:38.993 31545-31545/info.dodata.soundmeter E/my: Save 5 104
2022-08-29 11:13:40.158 31545-31545/info.dodata.soundmeter E/my: Save 6 105

【问题讨论】:

  • 您需要将您的临时工置于一种状态,例如您的刷新状态。
  • 谢谢!但是我把temp 换成了remember,你可以看到代码B。
  • 我没有看到代码 A 和 B 之间有任何区别。
  • 代码 A 包含 Log.e("my", "Load $temp ${refresh.value}")
  • 那么,删除日志后,代码是否按预期工作?

标签: android android-jetpack-compose


【解决方案1】:

在代码 A

1- temp 中的更改无法在重组后保留下来的原因是它是用 remember 包裹的原语。记住通过重组存储相同的对象。您读取的值 2 是重组前的值。

如果您将原语存储在 Object

class MyObject(var value:Int)
var myObj = remember{MyObject(0)}
myObj.value++

将在每次重组时返回更新值

What does Jetpack Compose remember actually do, how does it work under the hood?

2- 整个 Composable 被重组的原因是范围重组。您在 Button 范围内和 Composable 范围内读取 refresh.value,日志位于顶部

对于每个返回 Unit 的非内联可组合函数, Compose 编译器生成的代码将函数体包装在一个 重组范围。当重组范围无效时, 运行时将确保(整个)函数体得到重组 (重新执行)在下一帧之前。函数是一个天然的分隔符 对于可重新执行的代码块,因为它们已经有 定义明确的入口点和出口点。

Jetpack Compose Smart Recomposition

Why does mutableStateOf without remember work sometimes?

https://dev.to/zachklipp/scoped-recomposition-jetpack-compose-what-happens-when-state-changes-l78

【讨论】:

  • 所以出现这种情况有两个原因:1.原始变量,2.refresh.value在最外层范围内调用,对吧?
  • 原始值是 temp 没有更新的原因,记住存储相同的原始值或Object。但是,即使它是同一个对象,您也可以更新属性。这就是为什么 temp 没有更新,但是如果你将它包装在一个对象中,你会返回相同的实例但具有更新的值。所以这是一种绕过 remember returning same object 的方法。它被广泛用于不实例化和丢失当前值或不在每次重组时实例化内存重对象的方式
  • 整个 Composable 的重组也是因为读取了 Button 范围之外的值。当您读取 State 的值时,您的 Composable 的那部分会被重新组合。如果您检查第一个链接,您会发现即使我在另一个函数中更新了一个值,我的整个 Composable 都被重新组合了。这是因为作用域重组。任何不是 inline 并返回 Unit 的函数都被视为范围。因此,当您读取一个值时,Compose 会检查应该重新组合的最近范围。
【解决方案2】:

添加了一个日志来解释这段代码是如何工作的。

@Composable
fun CodeA() {
    var temp = remember {
        1
    }
    val refresh = remember {
        mutableStateOf(100)
    }
    Log.e("Test", "temp reset") // New added log
    Log.e("Test", "Load $temp ${refresh.value}")
    Button(
        onClick = {
            temp++
            refresh.value++

            Log.e("Test", "Save $temp ${refresh.value}")
        }
    ) {
        Text("OK $temp ${refresh.value}")
    }
}

如果我们看到,新添加的日志没有任何状态。 每次执行代码时它都会起作用。

Jetpack compose 进行最佳重组。所以它只会重构更新状态变化所需的代码。

请注意,只有状态更改才会触发重组。

当您在 Button 读取 refresh.value 之外没有日志时,重组将仅针对 Button 代码发生。但是一旦您在 Button 之外添加新的 Log 语句,整个 Composable 将被重新组合以反映状态变化。

应用程序中的所有状态都应定义为状态。 temp 应该是您期望的状态。


refresh.value日志输出

temp reset
Load 1 100
Save 2 101
temp reset
Load 1 101
Save 2 102
temp reset
Load 1 102
Save 2 103
temp reset
Load 1 103

输出 refresh.value log

temp reset
Save 2 101
Save 3 102
Save 4 103

【讨论】:

    【解决方案3】:

    AFAIK,根据定义 remember { } 就像它的名字一样,记住你的 lambda 在重组过程中产生的价值。当你只是给它原始值,如 Int、'String' 等,而 remember 正在以任何方式重组时,它将为你提供 lambda 中的原始值。

    另一方面,mutableState 或引擎盖下的其他状态 API 会将您的最后一个值写入 parcelable 并在重组发生时检索它。所以它就像一个包装器对象,将最后一个值保存到内存中,稍后使用remebber 重组时记住的同一对象检索它。我猜 Log.e("my", "Load $temp ${refresh.value}") 代码触发了重组,所以这就是为什么在你的代码 A 上 temp 每次重组时都会从 lambda 重置为它的返回值。

    【讨论】:

      【解决方案4】:

      刷新值++

      刷新值 = 刷新值+1

      两者的区别在于后者调用了[refresh.setter()]

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-05-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-12-05
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多