Jetpack Compose中remember和rememberUpdatedState之间的区别是什么?

22
我有点困惑,能否有人解释一下以下两者之间的区别:
val variable by remember { mutableStateOf() }

并且

val variable by rememberUpdatedState()

当我检查 rememberUpdatedStates 的源代码时,实际上看到的是:remember { mutableStateOf() }
@Composable
fun <T> rememberUpdatedState(newValue: T): State<T> = remember {
    mutableStateOf(newValue)
}.apply { value = newValue }

2
这篇文章帮助我更好地理解:https://proandroiddev.com/jetpack-compose-side-effects-iii-rememberupdatedstate-c8df7b90a01d - I'm a frog dragon
3个回答

23
当你的可组合被重新组合时,如果你不想进行一些繁重的计算/操作,那么就需要使用remember。另一方面,有时候你的操作可能会发生变化,所以你需要进行计算或更新记忆的值,以确保不使用初始计算中的过时数值。
fun <T> rememberUpdatedState(newValue: T): State<T> = remember {
    mutableStateOf(newValue)
}.apply { value = newValue }

rememberUpdatedState函数与使用remembermutableState触发重组的方式相同,当value发生变化时。

@Composable
private fun Calculation(input: Int) {
    val rememberUpdatedStateInput by rememberUpdatedState(input)
    val rememberedInput = remember { input }

    Text("updatedInput: $rememberUpdatedStateInput, rememberedInput: $rememberedInput")
}

var myInput by remember {
    mutableStateOf(0)
}

OutlinedButton(
    onClick = {
        myInput++

    }
) {
    Text("Increase $myInput")
}
Calculation(input = myInput)

这是一个非常基本的例子,展示了rememberrememberUpdatedState中的values如何改变。
一个更实际的例子是使用lambda表达式。
例如,假设您的应用程序有一个LandingScreen,在一段时间后消失。即使LandingScreen被重新组合,等待一段时间并通知时间已过去的效果也不应重新启动:
@Composable
fun LandingScreen(onTimeout: () -> Unit) {

    // This will always refer to the latest onTimeout function that
    // LandingScreen was recomposed with
    val currentOnTimeout by rememberUpdatedState(onTimeout)

    // Create an effect that matches the lifecycle of LandingScreen.
    // If LandingScreen recomposes, the delay shouldn't start again.
    LaunchedEffect(true) {
        delay(SplashWaitTimeMillis)
        currentOnTimeout()
    }

    /* Landing screen content */
}

在这个例子中,LaunchedEffect只被调用一次,但是这个LandingScreen函数可以被重新组合,并且可能需要你改变onTimeOut,所以使用rememberUpdatedState可以确保最新的onTimeout在延迟之后被调用。

18
rememberrememberUpdatedStates的区别如下:

remember

记住计算生成的值。在组合期间将仅对计算进行评估。重新组合始终返回由组合生成的值。

当使用remember时,每次重新组合调用都只会返回最初在第一次调用remember时计算出的相同值。您可以将其视为无法在将来的参考中更新的只读状态,而重新计算将引用初始评估。


rememberUpdatedStates

记住mutableStateOf,并在每次rememberUpdatedState调用的重新组合时将其值更新为newValue。

在长时间生存的lambda或对象表达式引用组合期间计算的参数或值时,应使用rememberUpdatedState。重新组合将更新结果状态而不会重新创建长期存在的lambda或对象,使该对象保持持久性而无需取消和重新订阅,或重新启动可能昂贵或难以重建和重新启动的长时间运行操作。

在这里,有时你的计算可能需要一段时间,计算可能会相当慢。在这种情况下,你会得到最新的值,而不是每次重组都会对lambda产生影响的值,以便你可以参考计算产生的最新值。

通过使用这种方法,您可以确保在每次重新组合时更新UI,而不必重新创建长期存在的lambda或重新启动长期存在的操作,这是在remember方法的lambda回调期间可能发生的情况。


1
一个很好的解释,但现在我对常规valrememberUpdatedStates之间的区别有一个问题。 - Arpit Patel
1
@ArpitPatel我知道我不应该这样说,但为了让你理解,你可以将常规的“remember”视为不可变状态持有者(类似于val),而“rememberUpdatedStates”是可变的(类似于var),如果需要的话,在首次加载后提供连续的更改。 - Jeel Vankhede
2
@ArpitPatel 如果您只需要在组合中直接使用该值,而不需要在回调或lambda(例如:LaunchedEffect)中引用它,这些可能在组合范围之外执行,您可以使用常规的val。但是,如果您需要确保回调或lambda始终具有最新的状态,即使在组合之外调用时也是如此,请使用rememberUpdatedState - Mofe Ejegi

2
Jeel给出了一个很好的答案,所以我只是添加一些示例代码。
我创建了一个示例,演示了rememberUpdatedState()LaunchedEffect的影响,包括使用和不使用它。这个示例非常简单:
  1. 用户安排了一条消息,该消息将在lambda中传递给LaunchedEffect

  2. 过了一段时间,用户创建了另一条消息,该消息将在lambda中传递给与之前消息完全相同的LaunchedEffect

  3. 如果用户选择使用rememberUpdatedState()LaunchedEffect将使用新的lambda进行更新。如果他们不选择使用,LaunchedEffect将使用初始lambda运行,并忽略后续的重新组合。

下面是执行“决策”的代码,最终要么更新LaunchedEffect,要么不更新:
@Composable
fun ShowToast(
    useRememberUpdatedState: Boolean,
    message: (() -> String)? = null
) {
    val context = LocalContext.current
    var actualTrueMessage: State<(() -> String)?>? = null
    if (useRememberUpdatedState) {
        actualTrueMessage = rememberUpdatedState(message)
    }
    LaunchedEffect(Unit) {
        delay(MESSAGE_DELAY)
        Toast.makeText(
            context,
            actualTrueMessage?.value?.invoke() ?: message?.invoke(),
            Toast.LENGTH_SHORT
        ).show()
    }
}

请注意,在我的示例中,我特别使用LaunchedEffectUnit键来启动它,以便在组合和重新组合时仅启动一次。
以下是它的样子:

LaunchedEffect with and without the usage of rememberUpdatedState()


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接