Composition: суть, построение UI-дерева в Compose

Автор: IT Sectr Опубликовано: 2026-06-27 Время чтения: 7 мин

Composition — это центральный процесс в Jetpack Compose, в ходе которого из описательных Composable-функций строится живое UI-дерево, отображаемое на экране. В отличие от View-системы Android, где разметка загружалась из XML и преобразовывалась в неизменяемые объекты, Composition работает как динамическая система: функции выполняются, создают слоты в памяти, формируют иерархию узлов и связывают её с состоянием. По данным Google Android Developers, 2026, понимание Composition критически важно для оптимизации производительности Compose-приложений.

Главное

  • Composition — это выполнение Composable-функций для построения UI-дерева
  • Слоты — ячейки памяти, хранящие параметры и состояние каждой функции
  • Позиция в Compose (Positional Memorization) привязывает состояние к месту в коде
  • Первый проход Composition создаёт начальное UI-дерево при запуске экрана
  • CompositionLocal передаёт данные через дерево без явных параметров

Что такое Composition в Jetpack Compose

Composition — это процесс выполнения Composable-функций, в результате которого формируется внутреннее представление пользовательского интерфейса в виде дерева узлов. Каждый узел этого дерева соответствует либо встроенному компоненту (Text, Button, Image), либо вызову пользовательской Composable-функции. Composition не создаёт непосредственно View-объекты Android — он строит абстрактное описание, которое затем обрабатывается фазами Layout и Drawing.

Ключевая особенность Composition — его энергонезависимость (restartability). Каждая Composable-функция в составе композиции может быть перезапущена в любой момент, если changed её входные параметры или прочитанные ей state-объекты. Система не перезапускает всё дерево целиком — только те функции, которые действительно зависят от изменившихся данных.

Технически Composition управляется через Composer — внутренний движок, который Kotlin-компилятор встраивает в каждую Composable-функцию. Composer записывает в слоты (группы позиций) информацию о том, какие функции были вызваны, с какими параметрами и в каком порядке. При последующих вызовах Composer сравнивает новые данные с сохранёнными и принимает решение о перезапуске.

Как строится UI-дерево в процессе Composition

Процесс построения UI-дерева начинается с вызова метода setContent внутри Activity или Fragment. Этот метод создаёт начальный Composition и запускает выполнение корневой Composable-функции. Далее каждая вложенная Composable-функция добавляет свои узлы в дерево, формируя иерархию: Row содержит Text и Button, Column содержит Image и Card, и так далее.

Каждый узел дерева получает уникальный ключ позиции, основанный на его положении в исходном коде. Этот ключ используется для идентификации узла при повторных выполнениях. Ключ позиции — причина, по которой порядок вызова Composable-функций не должен зависеть от условий: если в одном запуске вызвана A -> B, а в следующем B -> A, Compose не сможет сопоставить старые и новые узлы.

kotlin
@Composable
fun AppScreen() {
    Column {                     // Column node (position 1)
        HeaderSection()            // HeaderSection node (position 2)
        ContentSection()           // ContentSection node (position 3)
        FooterSection()            // FooterSection node (position 4)
    }
}

@Composable
fun HeaderSection() {
    Row {                       // Row node (position 2.1)
        Text("Title")         // Text node (position 2.2)
        Icon(...)                // Icon node (position 2.3)
    }
}

В этом примере каждый вызов получает позицию, основанную на порядке в коде. Column (позиция 1) содержит три дочерних узла (позиции 2, 3, 4). HeaderSection добавляет ещё два дочерних узла (2.1, 2.2, 2.3). Если в следующей рекомпозиции ContentSection будет вызван до HeaderSection, Composer не сможет корректно сопоставить узлы — отсюда правило: порядок вызовов Composable-функций должен быть стабильным.

Управление состоянием в Composition

Состояние в Composition управляется через объекты типа State<T>. Когда Composable-функция читает значение из State через делегированное свойство (by), она регистрирует зависимость от этого State. При изменении значения все функции, прочитавшие этот State, помечаются для перезапуска в следующей фазе композиции.

Механизм регистрации зависимостей называется snapshot system. Каждый раз, когда State меняется, снимок (snapshot) фиксирует все изменения и уведомляет Composer, какие функции зависят от этого State. Важно понимать: чтение State внутри не-Composable кода (например, в лямбде onClick) не регистрирует зависимость — только чтение внутри Composable-функции или в лямбдах, выполненных в контексте композиции.

Snapshot system работает транзакционно: несколько изменений State в рамках одного события объединяются в одну транзакцию, что предотвращает множественные рекомпозиции. Это особенно важно при обработке жестов: за одно движение меняется несколько State-объектов, но Compose выполняет только одну рекомпозицию.

kotlin
@Composable
fun StateExample() {
    var text by remember { mutableStateOf("Hello") }
    var isVisible by remember { mutableStateOf(true) }

    Column {
        Text(text)  // registers dependency on text

        if (isVisible) {  // registers dependency on isVisible
            TextField(value = text, onValueChange = { text = it })
        }

        Button(onClick = { isVisible = !isVisible }) {
            Text(if (isVisible) "Hide" else "Show")
        }
    }
}

Изменение text приводит к рекомпозиции только Column, Text и TextField. Column, Button и условие isVisible остаются без изменений. Такая изолированность рекомпозиции — ключевое преимущество Compose перед системами, перерисовывающими весь экран целиком. Каждая Composable-функция отслеживает только те State-объекты, которые она непосредственно читает.

Composition vs Recomposition: ключевые отличия

Composition (композиция) и Recomposition (рекомпозиция) — два разных режима выполнения Composable-функций. Composition происходит один раз при создании экрана: система выполняет все Composable-функции с начальными значениями и строит первоначальное UI-дерево. Recomposition происходит многократно при изменении данных: система перезапускает только те функции, которые зависят от изменившегося состояния.

Режим Composition активирует все узлы дерева, выделяет слоты для каждой функции, регистрирует всех потомков. Recomposition же работает выборочно: Compose сравнивает новые и старые значения параметров каждой функции, и если они не изменились — функция не выполняется (skipping).

Composition и Recomposition различаются стоимостью. Первый Composition дороже, так требует полного построения дерева и выделения слотов. Recomposition дешевле, особенно если большинство функций stable — их параметры сравниваются по equals, и Compose пропускает их вызов. Для максимальной производительности нужно стремиться к тому, чтобы большинство рекомпозиций затрагивало как можно меньше функций.

ХарактеристикаCompositionRecomposition
Когда происходитОдин раз, при первом отображенииМногократно, при изменении данных
ОбъёмВсё дерево целикомТолько изменившиеся функции
Сравнение параметровНе выполняетсяВыполняется для skipping
Создание слотовДа, все слоты создаютсяТолько для новых узлов

CompositionLocal: передача данных через дерево

CompositionLocal — механизм неявной передачи данных через дерево композиции. Он решает проблему, когда параметр нужно передать через десятки вложенных Composable-функций, которые его не используют напрямую. Вместо явной цепочки параметров данные устанавливаются на верхнем уровне и читаются в любой вложенной функции через CompositionLocal.current.

Материальная тема MaterialTheme — самый известный пример CompositionLocal. Все компоненты Compose читают цвета, типографику и формы через MaterialTheme.colorScheme, MaterialTheme.typography, MaterialTheme.shapes, не получая их через параметры. Разработчик может создавать свои CompositionLocal для таких данных, как текущий пользователь, настройки локализации или конфигурация экрана.

Важное ограничение: CompositionLocal не следует использовать для часто меняющихся данных (положение скролла, текст в поле ввода). Компонент, читающий CompositionLocal, перезапускается при каждом изменении значения, поэтому для динамических данных лучше использовать явные параметры или State. CompositionLocal оптимален для конфигурационных данных, которые меняются редко или не меняются вовсе.

kotlin
val LocalUser = compositionLocalOf<User?> { null }

@Composable
fun AppRoot(user: User, content: @Composable () -> Unit) {
    CompositionLocalProvider(LocalUser.provides(user)) {
        content()
    }
}

@Composable
fun UserAvatar() {
    val user = LocalUser.current  // reading without explicit parameter
    AsyncImage(model = user?.avatarUrl, contentDescription = "Avatar")
}

CompositionLocalProvider создаёт область видимости, внутри которой LocalUser.current возвращает заданное значение. UserAvatar читает пользователя без явной передачи параметра через промежуточные функции. Это особенно ценно в глубоких иерархиях, где данные нужны только в нескольких листовых узлах.

Часто задаваемые вопросы

Что произойдёт, если изменить State во время Composition?

Изменение State во время Composition планирует новую рекомпозицию, которая выполнится после завершения текущей. Зацикливания не происходит: Compose гарантирует, что каждая рекомпозиция выполняется в отдельной транзакции snapshot-системы.

Сколько времени занимает Composition сложного экрана?

На современных устройствах Composition экрана с 50–100 Composable-функциями занимает 1–5 мс. Google рекомендует укладываться в 16 мс для кадра 60fps. Если Composition превышает этот лимит, используйте LazyColumn или разбейте экран на меньшие функции.

Можно ли запустить Composition вручную?

Прямой ручной запуск Composition невозможен — он управляется Composer автоматически. Однако можно принудительно запланировать рекомпозицию, изменив State или вызвав invalidate() у корневого композита, если есть доступ к CompositionContext.

Чем Composition отличается от View-иерархии в классическом Android?

View-иерархия — это неизменяемое дерево Java-объектов, которое создаётся один раз. Composition — виртуальное дерево, которое перестраивается при каждом изменении данных. View сохраняет своё состояние в переменных экземпляра, Composition — в слотах, привязанных к позиции вызова функции.

Как Composition обрабатывает удаление узлов?

Если Composable-функция перестала вызываться (например, условие if стало false), Composition удаляет её узел и вызывает очистку DisposableEffect. При повторном появлении (if снова true) создаётся новый узел — старый не восстанавливается.

Итоги

  • Composition — процесс выполнения Composable-функций для построения UI-дерева с привязкой к состоянию
  • Composer управляет слотами, записывает вызовы функций и сравнивает параметры при рекомпозиции
  • Snapshot system регистрирует зависимости функций от State и объединяет изменения в транзакции
  • Composition выполняется один раз при старте, Recomposition — при изменении данных
  • CompositionLocal передаёт конфигурационные данные через дерево без явной цепочки параметров
  • Позиция вызова функции служит её уникальным идентификатором в дереве композиции
  • Рекомендация: делайте Composable-функции небольшими, с иммутабельными параметрами для эффективного skipping

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

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

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