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-класс. Lambda всегда стабильна, так как её 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 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

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