Подъём состояния (state hoisting)

Учимся выносить состояние из компонента наверх, чтобы делать переиспользуемые и тестируемые composable.

State hoisting — паттерн, при котором состояние выносится из composable в вызывающий код: компонент получает значение через параметр и сообщает об изменениях через колбэк.

Однонаправленный поток данных

В Compose данные текут вниз, события — вверх. Компонент получает value сверху и не меняет его сам, а сообщает «пользователь хочет изменить» через колбэк onValueChange. Решение, как реагировать, принимает владелец состояния.

@Composable
fun NameField(value: String, onValueChange: (String) -> Unit) {
    TextField(value = value, onValueChange = onValueChange)
}

Сам NameField не хранит текст. Он только показывает то, что ему дали, и сообщает о вводе. Это делает его управляемым (stateless) и переиспользуемым. Запомните простое правило обозначений: параметр value (или checked, selected) — это «данные вниз», а парный к нему колбэк onValueChange (или onCheckedChange) — «событие вверх». Вы встретите эту пару почти у каждого интерактивного компонента Compose.

Кто хранит состояние

Состояние держит компонент выше по дереву:

@Composable
fun NameScreen() {
    var name by remember { mutableStateOf("") }
    NameField(value = name, onValueChange = { name = it })
}

Так данные текут от NameScreen вниз в NameField (через value), а событие ввода поднимается обратно вверх (через onValueChange). Это и есть однонаправленный поток.

Схема потока

NameScreen (хранит name)
    | value = name        (данные вниз)
    v
NameField (показывает)
    ^
    | onValueChange(it)   (событие вверх)
    |

Как работает под капотом

Подъём состояния не магия — это обычная передача параметров. Выгода в том, что один источник истины (name в NameScreen) управляет всем. Stateless-компонент проще тестировать: ему просто передают значение и проверяют отрисовку. А ещё его можно переиспользовать в разных экранах с разными владельцами состояния.

Частые ошибки

  • Хранить состояние внутри низкоуровневого компонента, который хочется переиспользовать — он становится негибким.
  • Поднимать состояние «слишком высоко», в самый верх дерева, когда им пользуется лишь один маленький компонент — лишние перерисовки и связность.
  • Менять value внутри компонента напрямую вместо вызова onValueChange — нарушается однонаправленный поток.

Итог

  • Данные текут вниз через value, события — вверх через колбэки.
  • Stateless-компоненты переиспользуемы и легко тестируются.
  • Поднимайте состояние ровно до того уровня, где им реально нужно управлять.
Проверьте себя
1. Что такое state hoisting?
AХранение состояния в самом низком компоненте
BВынос состояния наверх: компонент получает value и сообщает об изменениях через колбэк
CСохранение состояния в базу данных
DАвтоматическое создание состояния компилятором
2. Как устроен однонаправленный поток данных в Compose?
AДанные и события текут в обе стороны хаотично
BДанные текут вниз через параметры, события поднимаются вверх через колбэки
CДанные текут только вверх
DСостояние синхронизируется через глобальные переменные