mutableStateOf: создание наблюдаемого состояния и реактивность Compose

Автор: IT Sectr Опубликовано: 2026-06-28 Время чтения: 7 мин

mutableStateOf — это функция в Jetpack Compose, которая создаёт контейнер изменяемого наблюдаемого состояния. Когда значение внутри этого контейнера меняется, Compose автоматически запускает рекомпозицию всех компонентов, читающих это состояние. Без mutableStateOf UI не мог бы реактивно обновляться при изменениях данных. По данным Google Android Developers, 2026, mutableStateOf является основным строительным блоком для локального состояния в Compose.

Главное

  • mutableStateOf создаёт контейнер MutableState, отслеживаемый Compose Runtime
  • Рекомпозиция запускается автоматически при изменении value этого State-объекта
  • Делегирование через var позволяет использовать mutableStateOf без обращения к .value
  • Ключи в remember(mutableStateOf) не нужны — State сам уведомляет Compose об изменениях
  • Snapshot система гарантирует консистентность чтения в многопоточном окружении

Что такое mutableStateOf в Jetpack Compose

mutableStateOf — это функция из пакета compose.runtime, которая создаёт объект MutableState, хранящий значение и способный уведомлять Compose Runtime об изменениях. Сигнатура: fun mutableStateOf(value: T, policy: SnapshotMutationPolicy = structuralEquality()): MutableState. Параметр policy определяет, когда изменение считается значимым — при структурном равенстве, при ссылочном равенстве или никогда.

MutableState — это интерфейс с единственным свойством value: геттер для чтения и сеттер для записи. При вызове сеттера Compose Runtime фиксирует изменение в снапшоте и помечает все Composable-функции, читающие эту State-переменную, как требующие рекомпозиции. Этот процесс происходит синхронно в рамках одного snapshot-цикла, что исключает промежуточные состояния при каскадных изменениях.

Parameter policy — второй аргумент mutableStateOf, определяющий поведение при сравнении. structuralEquality() проверяет equals() — это поведение по умолчанию. referentialEquality() проверяет === (ссылочное равенство). neverEqual() считает каждое присваивание изменением. Выбор policy влияет на то, будет ли запускаться рекомпозиция при присвоении "того же самого" значения.

Синтаксис и способы объявления mutableStateOf

Самый простой способ объявить наблюдаемое состояние — использовать mutableStateOf с remember. Без remember при каждой рекомпозиции создавался бы новый State, и все предыдущие изменения терялись бы. remember гарантирует, что один и тот же MutableState переживает серию рекомпозиций, пока Composable-функция остаётся в составе композиции.

kotlin
@Composable
fun Counter() {
    // Without delegation: read/write via .value
    val count = remember { mutableStateOf(0) }
    Button(onClick = { count.value++ }) {
        Text("Count: ${count.value}")
    }
}

@Composable
fun CounterDelegated() {
    // With delegation: var + by = Property Delegation
    var count by remember { mutableStateOf(0) }
    Button(onClick = { count++ }) {
        Text("Count: $count")
    }
}

Разница между двумя вариантами — синтаксическая. Property Delegation (by) использует соглашение Kotlin: компилятор генерирует вызовы getValue() и setValue() для чтения и записи. Это эквивалентно прямому обращению к count.value, но выглядит как работа с обычной переменной. Оба варианта функционально идентичны: Compose отслеживает чтение в геттере и запись в сеттере независимо от формы записи.

ФормаКодЧтениеЗапись
Без делегированияval count = mutableStateOf(0)count.valuecount.value = n
С делегированиемvar count by mutableStateOf(0)countcount = n

Делегированные свойства и var

Механизм делегированных свойств Kotlin — это не особенность Compose, а встроенная возможность языка. Любой класс может реализовать операторы getValue(thisRef, property) и setValue(thisRef, property, value), после чего его экземпляр можно использовать с ключевым словом by. MutableState именно так и работает: getValue возвращает текущее значение, а setValue присваивает новое.

Важное различие: val и var. mutableStateOf можно присвоить как val, так и var. В случае val (val count = mutableStateOf(0)) сам объект MutableState неизменяем, но его свойство value можно менять. В случае var (var count by mutableStateOf(0)) делегирование даёт иллюзию работы с примитивом, но на самом деле сеттер вызывает setValue на MutableState. Выбор между val и var — это выбор между явным и неявным доступом к .value.

State Delegation — это синтаксический сахар, который упрощает код, но не меняет механику. Компилятор Kotlin транслирует var x by mutableStateOf(0) в getter/setter, которые вызывают mutableStateOf.getValue() и mutableStateOf.setValue(). В сгенерированном байткоде разницы между val и var с by нет — оба работают через один и тот же MutableState-контейнер.

kotlin
    // Custom delegate for Compose State
class ValidatedState<T>(initialValue: T) {
    private val state = mutableStateOf(initialValue)

    operator fun getValue(thisRef: Any?, property: KProperty<*>) = state.value

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        if (value != state.value) {
            state.value = value
        }
    }
}

@Composable
fun Test() {
    var text by remember { ValidatedState("") }
}

Snapshot система: как mutableStateOf работает внутри

Snapshot — это механизм Compose Runtime, обеспечивающий консистентность чтения State при параллельных изменениях. Когда Composable-функция читает mutableStateOf, снимок фиксирует текущее значение. Если во время композиции другое изменение записывает в тот же State, снимок видит запись, но не позволяет читать несогласованные данные — чтение всегда возвращает значение, актуальное на момент начала снимка.

При вызове сеттера mutableStateOf.value = newValue Compose Runtime не сразу запускает рекомпозицию. Вместо этого изменение регистрируется в текущем снапшоте. Когда снапшот применяется (на границе фрейма), Compose проходит по списку изменённых State и помечает читающие компоненты как Invalid. Только в следующем фрейме запускается рекомпозиция. Это гарантирует, что UI не перерисовывается десятки раз при каскадных изменениях.

Глобальные и локальные снапшоты: по умолчанию mutableStateOf работает в глобальном снапшоте, который применяется автоматически. Можно создать локальный снапшот через Snapshot.takeSnapshot() для изолированного чтения без побочных эффектов. Это используется внутри Modifier, где нужно прочитать State, но не подписываться на изменения. Такой подход оптимизирует производительность и предотвращает неожиданные рекомпозиции.

Примеры использования mutableStateOf

Рассмотрим реальный сценарий — форма логина с тремя полями: email, password и статус загрузки. Все три поля используют mutableStateOf, но с разными policy и разной вложенностью. email использует делегирование, password — прямой доступ.

kotlin
data class LoginState(
    val email: String = "",
    val password: String = "",
    val isLoading: Boolean = false,
    val error: String? = null
)

@Composable
fun LoginForm(onLogin: (String, String) -> Unit) {
    // Single State for form, policy = referentialEquality
    var formState by remember {
        mutableStateOf(LoginState(), SnapshotMutationPolicy.referentialEquality())
    }

    val isValid = remember(formState) {
        formState.email.contains("@") && formState.password.length() >= 6
    }

    Column(modifier = Modifier.padding(16.dp)) {
        OutlinedTextField(
            value = formState.email,
            onValueChange = { formState = formState.copy(email = it) },
            label = { Text("Email") }
        )
        OutlinedTextField(
            value = formState.password,
            onValueChange = { formState = formState.copy(password = it) },
            label = { Text("Password") },
            visualTransformation = PasswordVisualTransformation()
        )
        Button(
            onClick = { onLogin(formState.email, formState.password) },
            enabled = isValid
        ) {
            Text("Login")
        }
    }
}

В этом примере mutableStateOf используется с кастомной data-классом LoginState и policy referentialEquality. Это значит, что рекомпозиция запустится только при присвоении нового экземпляра LoginState через copy(). isValid вычисляется на основе formState и пересчитывается только при его изменении. Такой подход даёт чёткий контроль над рекомпозициями: каждое поле формы меняется только через создание новой копии.

Часто задаваемые вопросы

Чем отличается mutableStateOf от StateFlow?

mutableStateOf — это Compose-специфичный контейнер, работающий внутри снапшотов. StateFlow — из kotlinx.coroutines.flow, который не привязан к Compose. mutableStateOf автоматически запускает рекомпозицию, StateFlow требует collectAsState(). Для UI-состояния внутри Composable предпочтительнее mutableStateOf.

Можно ли использовать mutableStateOf вне @Composable функций?

Да, mutableStateOf можно вызывать вне Composable-функции, но он не будет отслеживаться. Для работы реактивности в UI нужно читать State внутри Composable. Многие ViewModel используют MutableStateField (обёртку над mutableStateOf) для передачи состояния в UI через StateFlow.

Что произойдёт при одновременном изменении State из двух потоков?

Snapshot система гарантирует консистентность: каждая рекомпозиция видит согласованное состояние на момент начала снимка. Изменения из разных потоков применяются атомарно на границе фрейма, что исключает race condition при чтении внутри одной композиции.

Как сбросить mutableStateOf до начального значения?

Присвойте новое значение: count.value = 0 (или count = 0 при делегировании). Если нужно полное пересоздание State — используйте remember с ключом: remember(key) { mutableStateOf(initial) } — при изменении key State будет создан заново.

Влияет ли mutableStateOf на производительность при частых изменениях?

Композер использует снапшоты, которые группируют изменения: даже при сотне присваиваний в одном фрейме рекомпозиция выполняется только один раз. Для сверхчастых обновлений (анимации) используйте Animatable или animate*AsState — они оптимизированы для кадровых обновлений.

Итоги

  • mutableStateOf создаёт наблюдаемый контейнер MutableState, отслеживаемый Compose Runtime
  • Делегирование через by упрощает код, но не меняет механику работы State
  • Snapshot система гарантирует консистентность чтения и предотвращает лишние рекомпозиции
  • Policy определяет, когда изменение считается значимым — structuralEquality, referentialEquality, neverEqual
  • remember обязателен для сохранения State между рекомпозициями внутри Composable
  • Рекомендация: используйте mutableStateOf с делегированием для UI-состояния и referentialEquality для data классов
  • Избегайте: создания mutableStateOf без remember — каждое присваивание будет создавать новый объект

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также