MutableState — Compose 中的可观察状态与更新机制

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

MutableState — 是 Jetpack Compose 中的一个接口,表示可变可观察值的容器。它是 Compose 响应式系统的基础:每次 MutableState 的值通过 setter 改变时,Compose Runtime 会通知所有读取组件并触发重组(recomposition)。根据 Google Android Developers, 2026,理解 MutableState 是在声明式 UI 中正确处理状态的必要条件。

要点

  • MutableState — compose.runtime 接口,具有一个 value 属性(getter + setter)
  • State — 只读父接口,MutableState 添加了写入能力
  • 重组(Recomposition) 在活动 snapshot 周期内调用 value setter 时触发
  • SnapshotMutationPolicy 确定何时将更改视为对重组有意义
  • MutableIntState 等 — MutableState 的优化原始版本

什么是 Jetpack Compose 中的 MutableState

MutableState — 是来自 androidx.compose.runtime 包的一个接口,声明了一个属性:override var value: T。Getter 返回当前值,setter 写入新值并通知 Compose Runtime 关于更改。该接口继承自 State<T>,其中 value 仅可读。这种两层架构允许分离访问:只需要读取值的组件获得 State<T>,而所有者组件获得 MutableState<T>。

MutableState 的默认实现是内部类 SnapshotMutableStateImpl,它使用 snapshot 机制来跟踪更改。当 value setter 被调用时,当前 snapshot 记录写入并将所有注册的 ObservedScope(观察范围)标记为无效。这些范围(通常是可组合函数)将在下一帧中重组。整个过程同步且无阻塞地发生,得益于 Lock-free snapshot 架构。

State 与 MutableState:State — 是只读接口,用于组件的公共 API。当您将可组合函数的参数声明为 State<Int> 时,您保证该组件可以读取但不能更改状态。MutableState 在所有者组件内部使用。这种分离是 Compose 的基本实践之一,防止未经授权的更改。

State、MutableState 和派生接口的层次结构

Compose 中状态接口的层次结构有几个级别。顶层是 State<T>,具有只读 value。下面是 MutableState<T>,具有读写 value。然后是专门的原始版本:MutableIntState、MutableFloatState、MutableLongState、MutableBooleanState 等,它们避免了原始类型的自动装箱(boxing)。

MutableDoubleState 和 MutableLongState — 不太常见但存在的类型。集合接口:MutableListState — 用于跟踪列表内部变化,MutableStateMap — 用于映射。每个接口都针对特定场景进行了优化,并通过额外的集合操作方法扩展了基本的 MutableState。

SnapshotStateList 和 SnapshotStateMap — 是与 snapshot 兼容的可变列表和映射的实现。它们不仅允许跟踪值的替换,还允许跟踪内部更改:向列表添加元素、删除、修改现有元素。对于此类结构,mutableStateListOf() 和 mutableStateMapOf() 创建相应的可观察集合。

接口用途创建方法
State<T>只读容器
MutableState<T>读写容器mutableStateOf()
MutableIntState无装箱的原始 IntmutableIntStateOf()
MutableFloatState无装箱的原始 FloatmutableFloatStateOf()
SnapshotStateList可观察列表mutableStateListOf()
SnapshotStateMap可观察映射mutableStateMapOf()

SnapshotMutationPolicy:何时触发重组

SnapshotMutationPolicy — 是一个接口,确定 MutableState 的更改何时被视为有意义。mutableStateOf 接受 policy 作为第二个参数。标准实现:structuralEquality()(equals)、referentialEquality()(===)、neverEqualPolicy()(始终认为更改有意义)。对于自定义逻辑,您可以实现自己的 policy。

structuralEquality() — 默认行为。Compose 通过 equals() 比较新值与旧值。如果结果为 true — 不触发重组。这对于原始类型和 data class 很方便,其中两个具有相同字段的实例被视为相等。问题:如果 data class 包含 List,equals() 执行深度比较,对于大型列表可能代价高昂。

referentialEquality() — 通过 === 比较引用。仅当分配另一个对象时才触发重组,即使内容相同。这对于不可变的 data class 是最优的,其中每个新实例都保证意味着更改。neverEqualPolicy() — 始终认为更改有意义,不进行比较。当 setter 不经常调用且不需要花费时间在 equals 上时很有用。

kotlin
    // 策略对比实践
data class User(val name: String, val age: Int)

@Composable
fun UserProfile() {
    // structuralEquality:仅在数据更改时触发重组
    var user1 by remember {
        mutableStateOf(User("Alice", 30))
    }

    // referentialEquality:在每次赋值时触发重组
    var user2 by remember {
        mutableStateOf(User("Bob", 25),
            SnapshotMutationPolicy.referentialEquality())
    }

    // user1:copy() 相同字段不会触发重组
    // user2:即使 user2.copy() == user2 也会触发重组(新引用)
}

原始 State:MutableIntState、MutableFloatState、MutableLongState

原始 MutableIntState 等 — 是专门化的接口,存储原始类型而无自动装箱。普通的 MutableState<Int> 将 Int 存储为 Integer,每次写入都会在堆上创建一个对象。MutableIntState 存储 int(原始类型),完全消除了装箱开销。这在高频更新时尤其重要 — 计数器、滚动位置、动画值。

mutableIntStateOf()、mutableFloatStateOf()、mutableLongStateOf() — 创建原始 MutableState 的函数。接口分别称为 MutableIntState、MutableFloatState、MutableLongState。它们分别扩展了 MutableState<Int>、MutableState<Float> 和 MutableState<Long>,添加了 intValue 属性作为对原始类型的快速访问。在它们的内部实现中,使用 AtomicInteger 进行无锁读/写。

应用:计数器(Int)、滚动位置(Float offset)、时间戳(Long)。在大多数日常场景中,性能差异不可察觉,但在具有数千个元素和过渡动画的 LazyList 中,原始 State 提供了显著的提升。Google 建议对典型场景使用原始 State 而不是通用的 mutableStateOf。

kotlin
@Composable
fun ScrollCounter() {
    // 不好:每次更新时装箱
    var badCount by remember { mutableStateOf(0) }

    // 好:无装箱,原始存储
    var goodCount by remember { mutableIntStateOf(0) }

    // 使用方式相同
    Button(onClick = { goodCount++ }) {
        Text("Count: $goodCount")
    }
}

使用 MutableState 的实践示例

让我们考虑 TodoList 组件,其中 MutableState 以两种形式使用:作为输入状态的单独变量和作为动态任务列表的 SnapshotStateList。两者都使用委托来简化代码。

kotlin
data class TodoItem(val id: Int, val text: String, val isDone: Boolean = false)

@Composable
fun TodoScreen() {
    var inputText by remember { mutableStateOf("") }
    val items = remember { mutableStateListOf() }

    Column(modifier = Modifier.padding(16.dp)) {
        Row {
            TextField(
                value = inputText,
                onValueChange = { inputText = it }
            )
            Button(onClick = {
                if (inputText.isNotBlank()) {
                    items.add(TodoItem(items.size, inputText))
                    inputText = ""
                }
            }) { Text("添加") }
        }

        LazyColumn {
            items(items) { item ->
                Row(modifier = Modifier.fillMaxWidth().clickable {
                    val idx = items.indexOf(item)
                    items[idx] = item.copy(isDone = !item.isDone)
                }) {
                    Checkbox(checked = item.isDone, onCheckedChange = null)
                    Text(item.text)
                }
            }
        }
    }
}

mutableStateListOf 创建一个 SnapshotStateList — 一个可变列表,跟踪各个元素的变化。当调用 items.add() 和 items[n] = newValue 时,Compose 看到变更并仅重组那些已更改的 LazyColumn 元素。inputText — 普通的 MutableState<String>。两种 MutableState 类型(单个和集合)的组合是带有表单和列表的屏幕的典型模式。

常见问题

可以在没有 remember 的情况下使用 MutableState 吗?

没有 remember 的 MutableState 将在每次重组时重新创建。每次新的 mutableStateOf 调用都会创建一个新对象,旧值会丢失。始终使用 remember 在重组之间保持 State,除非 State 是在可组合函数外部创建的(例如在 ViewModel 中)。

如何将 MutableState 转换为普通变量?

通过 snapshot { } 在 snapshot 外部读取一次 .value。但这会禁用响应性 — 更改将不再触发重组。对于无需订阅的一次性读取,在 snapshot 内部使用 currentValue() 而不进行读取。

哪个更快:mutableStateOf 还是 mutableIntStateOf?

mutableIntStateOf 更快,因为它不需要将 int 装箱为 Integer。在每秒数千次更新(动画、滚动)时,差异可能达到分配时间的 30-50%。在罕见的更新(点击、文本输入)时,差异可以忽略不计。

可以将 MutableState 用作可组合函数的参数吗?

可以,但不推荐。不要传递 MutableState,而是传递 State(只读)加上 onValueChange lambda。这实现了 State Hoisting 模式并使组件可复用。接受 MutableState 的组件违反了单向数据流。

如何创建自定义的 MutableState 实现?

实现 MutableState 接口并提供带有 getter 和 setter 的 override var value。在 setter 中可以添加验证或日志记录。为了与 Compose Runtime 向后兼容,将您的实现包装在 snapshotFlow 中或使用 snapshotIncrement。

总结

  • MutableState — Compose 中可变可观察状态的基本接口
  • State — 用于传输数据而无权更改的只读版本
  • SnapshotMutationPolicy 管理更改时重组触发的条件
  • 原始 State(MutableIntState 等)消除了自动装箱开销
  • SnapshotStateList 和 SnapshotStateMap 跟踪集合的内部更改
  • 重组 在每次 value setter 调用时自动触发
  • 建议:使用 MutableState 拥有状态,使用 State 向下传递

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

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

讨论项目

另请阅读