State hoisting в Compose — это просто поднятие состояния наверх по дереву, из дочернего @Composable в его родителя.
Формула очень простая:
Компонент не хранит state, а получает его «сверху» и сообщает о изменениях «наверх».
И это одна из ключевых практик в Compose, потому что она обеспечивает один источник истины, повторное использование, тестируемость и предсказуемость UI.
Что такое State Hoisting на примере
Плохой пример без hoisting-а — компонент сам хранит состояние
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
Button(onClick = { count++ }) {
Text("Count = $count")
}
}
Здесь Counter сам хранит своё состояние и родитель никак не может контролировать count:
- не может сбросить его,
- не может задать начальное значение,
- не может синхронизировать с другим стейтом.
Hoisted state — выносим state в родителя
@Composable
fun Counter(
count: Int,
onIncrement: () -> Unit
) {
Button(onClick = onIncrement) {
Text("Count = $count")
}
}
А стейт теперь хранит родитель:
@Composable
fun CounterScreen() {
var count by remember { mutableStateOf(0) }
Counter(
count = count,
onIncrement = { count++ }
)
}
Вот это и есть state hoisting:
- состояние
countподнято изCounterвCounterScreen Counterстал stateless (управляемым извне)- он просто отрисовывает
countи репортит события через callback
Почему это ключевая практика в Compose
- Один источник истины (Single Source of Truth)
- Лёгкое управление и переиспользование
- Тестируемость
- Унифицированный подход “state down, events up”
Что будет, если state не поднимать?
Типичные проблемы:
- компонент невозможно контролировать снаружи (как
TextView, которому нельзя сказать: “не меняй текст сам”) - state начинает дублироваться в нескольких местах
- сложнее дебажить: “откуда взялось это значение?”
- трудно привязать к
ViewModel/Flow— придётся лепить костыли внизу