Как работает recomposition в Jetpack Compose? Что такое remember и rememberSaveable?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Механизм Recomposition в Jetpack Compose
Recomposition — это ключевой процесс в Jetpack Compose, который позволяет системе автоматически и эффективно обновлять UI при изменении состояния. Понимание recomposition критически важно для написания производительных и корректных Compose-приложений.
Основные принципы работы
Recomposition — это повторный вызов composable-функций (или их частей) при изменении входных данных (State). Композиция (Composition) — это первоначальное построение дерева UI путем вызова всех composable-функций. При изменении любого наблюдаемого состояния (например, через State или MutableState), Compose запускает recomposition для тех узлов дерева, которые зависят от этого измененного состояния.
- Интеллектуальное и инкрементальное обновление: Compose не перестраивает весь UI дерево целиком. Он использует "снимок" предыдущей композиции и сравнивает входные параметры (state) каждой composable-функции. Если параметры изменились, функция вызывается повторно (рекомпозируется). Если параметры остались прежними, Compose может полностью пропустить вызов этой функции и всех её потомков в дереве UI.
- Нестабильные входные параметры и Skippability: Чтобы оптимизация "пропуска" работала, параметры функции должны быть стабильными. Стабильный тип — это тип, у которого
equalsработает корректно, и его экземпляры не меняются после создания (например, все примитивы,String, и всеStateтипы). Если функция принимает нестабильный класс как параметр (например, обычный класс данных), она не сможет быть пропущена при recomposition, что может привести к потере производительности. - Позиционная мемоизация (Positional Memoization): Compose "помнит" результаты вызова функций для каждой позиции в дереве. При recomposition он знает, какие функции были вызваны в какой последовательности, и может сравнивать их входные данные именно для этих позиций.
Пример базового recomposition:
@Composable
fun CounterDisplay(count: Int) {
Text(text = "Count: $count") // Эта Text будет рекомпозироваться только когда `count` меняется.
}
@Composable
fun MyScreen() {
var countState by remember { mutableStateOf(0) }
Column {
CounterDisplay(countState) // Передает State как параметр.
Button(onClick = { countState++ }) { // Изменение состояния запускает recomposition для CounterDisplay.
Text("Increment")
}
}
}
Remember и RememberSaveable: сохранение состояния в композиции
Это ключевые инструменты для управления локальным состоянием composable-функции во время recomposition и конфигурационных изменений (например, поворот экрана).
Remember
remember — это функция, которая хранит результат вычисления во время жизни текущей композиции. Если composable-функция рекомпозируется, remember возвращает сохраненное значение, вместо повторного выполнения вычисления.
- Задача: Предотвращать повторные дорогостоящие вычисления или воссоздание объектов при каждом вызове функции (рекомпозиции).
- Срок жизни: Значение хранится до тех пор, пока composable-функция остается в композиции (т.е. пока она активна на экране). Если функция удаляется из дерева композиции (например, при навигации), значение теряется.
- Использование: Чаще всего используется вместе с
mutableStateOfдля создания локальногоState, который вызывает recomposition при изменении.
@Composable
fun ExpensiveComposable() {
// Значение будет вычислено только при первой композиции.
// При всех последующих recomposition будет возвращено сохраненное значение.
val expensiveResult = remember {
performVeryHeavyCalculation()
}
Text(text = "Result: $expensiveResult")
}
Ключевое использование для состояния:
@Composable
fun MyToggle() {
// Состояние `isChecked` сохраняется между recomposition.
// При изменении `isChecked` вызывает recomposition этой функции.
var isChecked by remember { mutableStateOf(false) }
Checkbox(
checked = isChecked,
onCheckedChange = { newValue -> isChecked = newValue }
)
}
RememberSaveable
rememberSaveable — это расширенная версия remember, которая добавляет возможность сохранять состояние через конфигурационные изменения системы (например, поворот экрана, изменение размера окна, процесс death).
- Задача: Сохранять не только между recomposition, но и между полными пересозданиями Activity/Fragment (композиции).
- Механизм: По умолчанию использует механизм
BundleAndroid (аналогично сохранению состояния вActivity.onSaveInstanceState()). Это означает, что тип данных должен поддерживать сохранение вBundle(примитивы,String,Parcelable,Serializableи т.д.). - Для сложных объектов: Можно предоставить собственный
Saverчерез параметрsaver, чтобы определить, как объект сохраняется и восстанавливается.
@Composable
fun UserInputField() {
// Текст в поле будет сохранен даже после поворота экрана.
var userText by rememberSaveable { mutableStateOf("") }
OutlinedTextField(
value = userText,
onValueChange = { newText -> userText = newText },
label = { Text("Enter text") }
)
}
Разница в поведении:
remember: Состояние теряется при конфигурационном изменении или когда composable удаляется из композиции.rememberSaveable: Состояние сохраняется при конфигурационных изменениях, но также теряется, когда composable окончательно удаляется из композиции (например, при навигации на другой экран).
Вывод
Для эффективной работы с Jetpack Compose необходимо:
- Писать composable-функции так, чтобы они могли быть пропущены при recomposition (использовать стабильные параметры).
- Использовать
rememberдля хранения локального состояния или результатов тяжелых вычислений во время текущей композиции. - Использовать
rememberSaveableдля того же, но с дополнительным требованием сохранять состояние при конфигурационных изменениях системы. - Всегда помнить, что изменение объекта, хранящегося в
rememberилиrememberSaveable(особенноState), является триггером recomposition для тех composable, которые его читают.