State Hoisting

State hoisting в Jetpack Compose — это паттерн управления состоянием, при котором состояние "поднимается" (hoisted) из дочернего компонента в родительский. Это делается для того, чтобы управление состоянием было вынесено за пределы компонента и могло изменяться или контролироваться извне.

Зачем нужен state hoisting?

Представь, что у тебя есть несколько компонентов, которые должны работать с одним и тем же состоянием. Если состояние будет управляться каждым компонентом отдельно, это создаст сложности при взаимодействии между ними. State hoisting помогает централизовать состояние, делая его доступным для всех нужных компонентов.

Как это работает?

State hoisting основывается на двух вещах:

  1. Состояние передаётся через параметры в функцию-компонент.
  2. Функция для обновления состояния также передаётся через параметры.

Это позволяет родителю контролировать состояние, а дочерние компоненты только отображают данные или вызывают функцию для их обновления.

Пример без state hoisting

Вот простой пример текстового поля, где состояние (введённый текст) управляется внутри компонента MyTextField:

@Composable
fun MyTextField() {
    var text by remember { mutableStateOf("") }

    TextField(
        value = text,
        onValueChange = { newText ->
            text = newText
        }
    )
}

Здесь состояние text хранится и обновляется внутри MyTextField. Это нормально, если это поле не должно взаимодействовать с другими частями приложения. Но что если текст нужно использовать в родительском компоненте?

Пример со state hoisting

Теперь мы поднимем состояние "наверх", чтобы родительский компонент контролировал его. Для этого нужно передать в MyTextField два параметра: текущее состояние и функцию для его обновления.

@Composable
fun MyTextField(text: String, onTextChange: (String) -> Unit) {
    TextField(
        value = text,
        onValueChange = { newText ->
            onTextChange(newText)
        }
    )
}

Теперь MyTextField больше не хранит состояние самостоятельно. Вместо этого оно передаётся извне через параметр text, а функция onTextChange обновляет это состояние.

Родительский компонент

Теперь родитель может управлять состоянием:

@Composable
fun Parent() {
    var text by remember { mutableStateOf("") }

    MyTextField(
        text = text,
        onTextChange = { newText ->
            text = newText
        }
    )
}

Здесь состояние text находится в компоненте Parent, и оно передаётся в дочерний MyTextField. Когда пользователь вводит новый текст, функция onTextChange обновляет это состояние в родительском компоненте.

Преимущества state hoisting

  1. Контроль состояния: Поднятие состояния позволяет контролировать его из родительского компонента, что упрощает логику приложения и улучшает тестируемость.
  2. Переиспользуемость компонентов: Компоненты становятся более гибкими и переиспользуемыми, так как не зависят от внутреннего состояния.
  3. Изоляция логики: Логика состояния и логика UI разделены. Компонент отвечает только за отображение, а не за управление состоянием.

Ключевые моменты

  • Компонент, использующий state hoisting, не должен сам управлять состоянием.
  • Родительский компонент несёт ответственность за состояние и его обновление.
  • Передача состояния и функции обновления является стандартным способом в Compose для управления состоянием извне.

Пример взаимодействия нескольких компонентов

Представим, что у нас есть текстовое поле и текстовый блок, которые должны показывать одно и то же значение:

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

@Composable
fun MyTextDisplay(text: String) {
    Text(text = text)
}

@Composable
fun Parent() {
    var text by remember { mutableStateOf("") }

    Column {
        MyTextField(text = text, onTextChange = { newText -> text = newText })
        MyTextDisplay(text = text)
    }
}

В этом примере родительский компонент Parent управляет состоянием text, а дочерние компоненты просто получают это состояние и/или вызывают функцию для его обновления.