Recomposition — що це, перебудова UI при зміні стану

Автор: IT Sectr Опубліковано: 2026-06-27 Час читання: 7 хв

Recomposition — це механізм Jetpack Compose, який автоматично перебудовує частини користувацького інтерфейсу при зміні даних, без ручного оновлення View-елементів. Коли змінна стану, від якої залежить Composable-функція, змінює значення, Compose перезапускає лише цю функцію, залишаючи решту UI-дерева недоторканою. За даними Google Android Developers, 2026, правильне розуміння Recomposition дозволяє скоротити кількість зайвих перемальовок на 40–60%.

Головне

  • Recomposition — перезапуск Composable-функцій при зміні їхніх вхідних даних або State
  • Skipping — пропуск функцій, чиї параметри не змінилися (порівняння по equals)
  • Stability визначає, чи може Compose пропустити функцію — стабільні типи порівнюються коректно
  • Smart Recomposition перезапускає лише мінімальний набір функцій, а не все дерево
  • Рекомпозиція не гарантує перемальовку — Layout і Drawing можуть пропустити фазу

Що таке Recomposition в Jetpack Compose

Recomposition — це повторне виконання Composable-функцій, які вже брали участь у Composition, з новими значеннями параметрів або стану. Основна мета рекомпозиції — синхронізувати UI-дерево з актуальними даними без перебудови всього інтерфейсу з нуля. На відміну від Composition, який відбувається одноразово, Recomposition може запускатися сотні разів за час життя екрана.

Recomposition працює за принципом smart invalidation: Compose відстежує, які State-об'єкти читає кожна Composable-функція, і позначає для перезапуску лише ті, чиї залежності змінилися. Це досягається завдяки snapshot-системі, яка фіксує всі операції читання State під час виконання, та Composer, який зіставляє ці залежності з конкретними функціями.

Важливо розуміти: рекомпозиція не означає негайну перемальовку екрана. Compose працює в три фази: Composition (побудова UI-опису), Layout (обчислення розмірів та позицій) і Drawing (відтворення на канві). Якщо після рекомпозиції розміри та положення елементів не змінилися, фазу Layout можна пропустити. Якщо зовнішній вигляд не змінився — пропускається Drawing. Ця трифазна архітектура забезпечує мінімальну вартість кожного оновлення UI.

Тригери рекомпозиції: що викликає перезапуск функцій

Існує три основні тригери рекомпозиції. Перший — зміна State-об'єкта, прочитаного в тілі Composable-функції. Коли mutableStateOf або derivedStateOf змінює своє значення, всі функції, які зареєстрували читання цього State в попередній композиції, позначаються для перезапуску.

Другий тригер — зміна параметрів Composable-функції при її виклику з батьківської функції. Якщо батьківська функція передала нове значення (наприклад, змінився текст або число), дочірня функція буде перезапущена, навіть якщо вона не читає State всередині себе. Compose порівнює нові та старі значення параметрів через equals, і якщо вони рівні — функція може бути пропущена.

Третій тригер — зміна CompositionLocal через CompositionLocalProvider. Всі функції, які читають CompositionLocal через .current, перезапускаються при зміні провайдера. Цей механізм використовує MaterialTheme: зміна теми (світла/темна) викликає рекомпозицію всіх компонентів, що читають MaterialTheme.colorScheme.

kotlin
@Composable
fun RecompositionDemo() {
    var counter by remember { mutableStateOf(0) }
    var text by remember { mutableStateOf("Hello") }

    Column {
        Text("Counter: $counter")  // recomposition when counter changes
        Text("Message: $text")    // recomposition when text changes

        Button(onClick = { counter++ }) {
            Text("+1")
        }
        Button(onClick = { text = "World" }) {
            Text("Change Text")
        }
    }
}

Натискання на кнопку +1 змінює counter, що викликає рекомпозицію лише першого рядка Text та самої Column. Другий рядок Text, який відображає text, не перезапускається. Така ізоляція — результат snapshot-системи: кожна Composable-функція знає лише про ті State-об'єкти, які вона прочитала.

Оптимізація рекомпозиції: практичні прийоми

Оптимізація рекомпозиції починається з правильного вибору структур даних. Використовуйте immutable-колекції (listOf, mapOf) замість змінюваних (mutableListOf). Compose порівнює параметри по equals, і якщо колекція змінилася, але equals повернув true — функція не перезапуститься. Для змінюваних колекцій використовуйте SnapshotStateList, який реалізує коректне відстеження змін на рівні елементів.

Другий прийом — винесення стабільних частин UI в окремі Composable-функції. Якщо частина екрана не залежить від стану, що часто змінюється, винесіть її в окрему функцію з параметрами. Коли приходить рекомпозиція, стабільна функція отримує ті ж параметри, Compose їх порівнює і пропускає виконання. Це вигідніше, ніж перезапускати цю частину у складі великої функції, де частина параметрів змінилася.

Третій прийом — ключі в LazyColumn. Завжди вказуйте key для item в LazyColumn, LazyGrid та інших лінивих контейнерах. Ключ дозволяє Compose ідентифікувати елементи при зміні списку: додаванні, видаленні або перестановці. Без ключа Compose перезапускає всі елементи списку при будь-якій зміні, що на великих списках дає помітну втрату продуктивності.

kotlin
// Optimized structure: stable parts extracted separately
@Composable
fun OptimizedScreen(items: List<Item>) {
    Column {
        Header()                         // does not depend on items — no recomposition
        Spacer(modifier = Modifier.height(8.dp))
        LazyColumn {
            items(items, key = { it.id }) { item ->
                ItemRow(item = item)   // recomposition only for changed items
            }
        }
    }
}

@Composable
fun Header() {
    Text("Item list", style = MaterialTheme.typography.headlineMedium)
}

@Composable
fun ItemRow(item: Item) {
    Text(item.title)
}

Skipping та Stability в Compose

Skipping — це механізм, при якому Compose пропускає виконання Composable-функції, якщо всі її параметри не змінилися. Щоб skipping працював коректно, типи параметрів повинні бути стабільними (stable). Компілятор Kotlin позначає як стабільні: примітивні типи (Int, Float, Boolean), String, функції-лямбди, а також класи, у яких всі поля стабільні та val.

Stability — це анотація @Stable або @Immutable, яку можна додати до користувацьких класів даних. Якщо клас містить змінюване поле (var), компілятор вважає його нестабільним, і Compose не зможе пропустити функції з такими параметрами. Для класів з var використовуйте @Stable, якщо ви гарантуєте, що повідомлення про зміну буде надіслано через snapshot-систему.

Перевірити stability можна через прапорець компілятора -P "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=. /reports". Він генерує звіт зі списком всіх Composable-функцій та їхніх параметрів із зазначенням stability. Якщо параметр нестабільний — skipping для цієї функції неможливий, і вона буде перезапускатися при кожній рекомпозиції батька.

ТипСтабільністьSkipping
Int, Float, BooleanСтабільнийТак
StringСтабільнийТак
ЛямбдаСтабільнийТак
data class з val-полямиСтабільнийТак
data class з var-полямиНестабільнийНі
List<String>НестабільнийНі

Зверніть увагу: List<String> вважається нестабільним, тому що це інтерфейс, а не конкретна реалізація. Використовуйте immutableListOf() з бібліотеки Kotlin Collections Immutable або обгортайте список у @Stable-клас. Лямбда завжди стабільна, оскільки її equals порівнює лише посилання, і при створенні нової лямбди в місці виклику батьківська функція також перезапускається.

Моніторинг рекомпозицій в Android Studio

Для моніторингу рекомпозицій Android Studio надає Layout Inspector з режимом Compose Recomposition Counts. У цьому режимі кожна Composable-функція відображає кількість рекомпозицій та причини перезапуску. Це дозволяє швидко знайти функції, які рекомпозуються занадто часто, і визначити кореневу причину — нестабільні параметри або зайві State-залежності.

Додаткові інструменти: Compose Metrics (збір статистики через instrumentation-тести) та Recomposition Timer (вимірювання часу виконання кожної функції). Google рекомендує вмикати ці інструменти на етапі профілювання та вимикати в релізних збірках, оскільки вони додають оверхед до 20% на кожну рекомпозицію.

При аналізі рекомпозицій шукайте патерни unnecessary recomposition: функція перезапускається, хоча її вихідний UI не повинен змінитися. Часта причина — використання лямбд без remember, коли щоразу створюється новий об'єкт лямбди, і Compose вважає параметр таким, що змінився. Рішення: обгортати лямбди в remember { } з фіксованими захватами.

kotlin
// Bad: new lambda on every parent recomposition
@Composable
fun Parent() {
    Child(onClick = { doSomething() })  // new lambda every time
}

// Good: remember stabilizes the lambda
@Composable
fun Parent() {
    val onClick = remember { { doSomething() } }
    Child(onClick = onClick)  // same reference
}

Часті запитання

Чи означає рекомпозиція перемальовку екрана?

Ні, рекомпозиція — це лише фаза Composition. Після неї виконуються Layout та Drawing. Якщо після рекомпозиції розміри та положення елементів не змінилися, Layout та Drawing можуть бути повністю пропущені, що економить ресурси GPU.

Як часто може відбуватися рекомпозиція?

При анімаціях рекомпозиція може запускатися до 120 разів на секунду (120fps). Для звичайної взаємодії — 10–60 разів на секунду. Важно, щоб кожна рекомпозиція вкладалася в бюджет кадру (8–16 мс), інакше додаток буде гальмувати.

Чому функція рекомпозується, хоча State не змінювався?

Причина — зміна параметра від батьківської функції. Батько перезапускається (зі своєї причини) і передає нове значення. Щоб уникнути цього, перевірте stability параметрів і використовуйте remember для стабілізації лямбд та обчислюваних значень.

Чи можна вимкнути рекомпозицію для конкретної функції?

Прямого вимкнення немає, але є примусовий skipping через readInComposition — State читається поза тілом функції, що не реєструє залежність. Використовуйте це обережно: функція не реагуватиме на зміни, що може призвести до застарілого UI.

Що дорожче: Composition чи Recomposition?

Composition дорожчий, оскільки створює всі слоти та вузли дерева з нуля. Recomposition перевикористовує існуючі слоти та лише оновлює їхні значення. На практиці Composition екрана займає 2–10 мс, а рекомпозиція одного елемента — 0.1–1 мс.

Підсумки

  • Recomposition — вибірковий перезапуск Composable-функцій при зміні їхнього State або параметрів
  • Snapshot system відстежує залежності функцій від State та планує рекомпозицію
  • Skipping можливий лише для функцій зі стабільними параметрами (@Stable або immutable)
  • Три тригери рекомпозиції: зміна State, зміна параметрів, зміна CompositionLocal
  • List<T> вважається нестабільним — використовуйте immutable-колекції для коректного skipping
  • Layout Inspector показує лічильники рекомпозицій для кожної функції
  • Рекомендація: виносьте стабільні частини UI в окремі функції та використовуйте remember для лямбд

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

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