remember — 核心概念,在重组之间保留值

作者: IT Sectr 发布日期: 2026-06-28 阅读时间: 8 分钟

remember — 是 Jetpack Compose 中的一个函数,它允许在重组之间保留值,防止每次重新启动 Composable 函数时重置状态。如果没有 remember,函数内部声明的所有变量将在每次 UI 更新时重置,使状态变得不稳定且无用。根据 Google Android Developers, 2026,正确使用 remember 是在声明式 UI 中正确管理状态的基础。

要点

  • remember 在组合槽中保留函数重组之间的值
  • 在 remember(key) 中决定何时应重新计算值
  • rememberSaveable 在屏幕旋转和应用程序最小化时保留值
  • derivedStateOf 创建派生状态,优化重组时的计算
  • 重置 remember 仅在函数退出组合时发生

Jetpack Compose 中的 remember 是什么

remember — 是 Jetpack Compose 中来自 androidx.compose.runtime 包的内置函数,它创建一个在 Composable 函数连续调用之间保持其值的对象。从技术上讲,remember 使用槽工作 — 为 UI 树中每个函数调用位置分配的特殊内存单元。只要函数保持在组合中(未通过 if 条件移除),remember 将在每次重组时返回保存的值。

remember 的语法很简单:在大括号内指定初始值的计算块。该块只执行一次 — 在函数的第一次组合时。所有后续重组返回已保存的值,而不重新调用该块。但是,如果指定了键(key 参数),则在键更改时重新执行该块,从而允许根据输入数据重新计算值。

remember 不是 Kotlin 魔法 — 它是一个带有 key 参数和计算 lambda 的简单函数。其内部实现使用 Composer 来读写槽。Kotlin 编译器在看到 remember 调用时,会生成访问当前 CompositionContext 并管理槽的代码。这意味着 remember 只能在 Composable 函数内部或在组合上下文中的另一个函数内部调用。

remember 的类型:remember、rememberSaveable、derivedStateOf

在 Jetpack Compose 中,remember 有三种主要变体,各有其用途。基本 remember 仅在当前进程的内存中保留值 — 屏幕旋转时(配置更改),Compose 重新创建 Composition,所有 remember 值都会重置。要在配置更改和进程停止时保留数据,请使用 rememberSaveable。

rememberSaveable 通过 SavedStateHandle 或 Parcelable 机制将值序列化到 Bundle 中。这允许在屏幕旋转、“不保留活动”调用甚至应用程序临时最小化时存活。但是,rememberSaveable 对保存的数据类型施加限制:它们必须是原始类型、Parcelable、Serializable 或支持 Saver 转换器。

derivedStateOf — 不是存储机制,而是优化。它创建一个 State,其值基于其他 State 对象计算。derivedStateOf 响应源状态的变化,但仅在有订阅者时重新计算值。如果当前重组未读取 derivedStateOf,则不执行计算,从而在频繁但无人需要的更新中节省资源。

kotlin
@Composable
fun RememberVariants() {
    // 1. 基本 remember:持续到离开组合
    val createdAt = remember { System.currentTimeMillis() }

    // 2. rememberSaveable:存活于屏幕旋转
    var username by rememberSaveable { mutableStateOf("") }

    // 3. derivedStateOf:仅在需要时计算
    val isButtonVisible = remember {
        derivedStateOf { username.length() > 3 }
    }

    Text("创建时间:$createdAt")
    TextField(value = username, onValueChange = { username = it })

    if (isButtonVisible.value) {
        Text("按钮将显示")
    }
}

remember 与 rememberSaveable:何时使用哪个

remember 和 rememberSaveable 之间的选择取决于在配置更改时数据保留的紧迫程度。对于旋转时丢失不重要的临时状态(动画、当前滚动位置、焦点状态) — 普通的 remember 就足够了。对于关键数据(表单中的文本、选定的项目、复选框) — 使用 rememberSaveable。

性能 rememberSaveable 低于普通的 remember,因为它需要序列化到 Bundle。仅对确实必须存活 Activity 重建的数据使用 rememberSaveable。对于其他一切 — 普通的 remember。过度使用 rememberSaveable 会导致屏幕旋转和应用程序之间切换时变慢。

如果您使用的类不支持 Parcelable 或 Serializable,请使用 Saver — 一个定义如何保存和恢复对象的转换器。Saver 由一对 lambda 描述:save(将对象转换为可保存类型)和 restore(从保存的数据恢复对象)。标准的 Saver 已为 mutableStateListOf 和 mutableStateMapOf 实现。

特性rememberrememberSaveable
重组时保留
旋转时保留
进程停止时保留
类型要求任意Parcelable、Serializable、Saver
性能中等

remember 在实际项目中的使用示例

让我们看一个典型场景 — 个人资料编辑屏幕,其中 remember 用于多个目的:存储表单字段的状态、计算派生值以及缓存计算成本高昂的操作。

kotlin
data class ProfileUiState(
    val name: String = "",
    val bio: String = "",
    val isSaving: Boolean = false
)

@Composable
fun ProfileEditor() {
    var state by rememberSaveable { mutableStateOf(ProfileUiState()) }
    val isValid = remember {
        derivedStateOf { state.name.isNotBlank() && state.bio.length() <= 500 }
    }

    val bioWarning = remember(state.bio) {
        if (state.bio.length() > 400) {
            "${500 - state.bio.length} chars left"
        } else null
    }

    Column(modifier = Modifier.padding(16.dp)) {
        OutlinedTextField(
            value = state.name,
            onValueChange = { state = state.copy(name = it) },
            label = { Text("名称") }
        )

        OutlinedTextField(
            value = state.bio,
            onValueChange = { state = state.copy(bio = it) },
            label = { Text("关于") }
        )

        bioWarning?.let { Text(it, color = MaterialTheme.colorScheme.error) }

        Button(onClick = { /* save */ },
            enabled = isValid.value) {
            Text("保存")
        }
    }
}

在该示例中,状态通过 rememberSaveable 保存 — 屏幕旋转时文本不会丢失。isValid 通过 derivedStateOf 计算,这防止了重组时的多余计算。bioWarning 使用带 bio 键的 remember — 这是一个昂贵的计算(可选,用于演示),仅在 bio 更改时重新计算,而不是在每次重组时。

派生状态和带键的 remember

派生状态是从其他 State 对象计算的值。与其在每次重组时计算它们并将 CPU 浪费在相同的结果上,remember 与 derivedStateOf 仅在源更改时计算值。这对于过滤、排序和数据聚合特别有用。

带键的 remember(remember(key) { calculation })— 另一种优化机制。如果键自上次重组以来未更改,则不执行计算块,并返回缓存的值。这对于缓存创建成本高昂的对象很方便:日期格式化、JSON 解析、创建大型不可变集合。

kotlin
@Composable
fun SearchResults(allItems: List<Item>, query: String) {
    // derivedStateOf:过滤器仅在输入更改时重新计算
    val filtered = remember {
        derivedStateOf {
            allItems.filter { it.title.contains(query, true) }
        }
    }

    // 带键的 remember:格式化统计信息仅在查询更改时重新计算
    val statsText = remember(query) {
        "Results for query \"$query\": ${filtered.value.size}"
    }

    Text(statsText)
    LazyColumn {
        items(filtered.value, key = { it.id }) { item ->
            Text(item.title)
        }
    }
}

filtered — 是一个 derivedStateOf,在 allItems 或 query 更改时自动重新计算。statsText 使用 remember(query) — 昂贵的字符串格式化仅在搜索查询更改时执行。derivedStateOf 和带键的 remember 的组合提供了最大性能:派生状态仅在需要时计算,复杂对象在键更改之前保持缓存。

常见问题

可以在 Composable 函数外部使用 remember 吗?

不可以,remember — 是来自 compose.runtime 包的函数,需要 CompositionContext。它只能在 @Composable 函数内部或从 Composable 调用的另一个函数内部调用。要在组合外部存储数据,请使用 ViewModel。

如果不在 remember 中指定键会怎样?

没有键的情况下,remember 只计算一次块 — 在第一次组合时。所有后续重组返回保存的值。如果需要在数据更改时重新计算值,请务必将它们指定为键:remember(data) { compute(data) }。

如何强制重置 remember?

remember 没有直接重置的方法。唯一的方法是将函数从组合中移除(例如,通过 if 条件隐藏),然后重新显示它。重新进入时,remember 块将重新执行,创建新的初始值。

remember 与 ViewModel 有何不同?

remember 将状态存储在 Composable 函数的槽中,并在函数处于组合中时存活。ViewModel 在屏幕的生命周期内存活。ViewModel 在旋转时保留并用于业务逻辑。remember — 用于不需要超出单个函数范围的本地 UI 状态。

remember 在 Preview 组件中有效吗?

是的,remember 在 @Preview Composable 函数中正常工作,因为 Preview 创建了完整的 CompositionContext。但是,rememberSaveable 在 Preview 中可能无法正常工作,因为预览环境中可能缺少 SavedStateHandle。

总结

  • remember 在组合槽中保留值并存活于函数的重组
  • rememberSaveable 额外在屏幕旋转和进程停止时保留值
  • derivedStateOf 使用惰性计算创建派生状态
  • 在 remember(key) 中控制缓存值的重新计算
  • 重置 remember 仅在函数从组合中移除时发生
  • 性能:derivedStateOf 和键防止重组时的多余计算
  • 建议:对输入字段使用 rememberSaveable,对计算值使用 remember + derivedStateOf

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读