← Назад к вопросам

Как устроен CompositionLocal под капотом?

2.8 Senior🔥 71 комментариев
#UI и вёрстка

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Устройство CompositionLocal под капотом

CompositionLocal — это механизм неявной передачи данных через дерево композиции Jetpack Compose, альтернативный явной передаче параметров через каждый компонент. Под капотом это реализовано с помощью системы лексических контекстов, которая тесно интегрирована с механизмом рекомпозиции Compose.

Базовая структура CompositionLocal

Основные элементы:

  • CompositionLocal<T> — контейнер, создаваемый через staticCompositionLocalOf или compositionLocalOf.
  • Provider (CompositionLocalProvider) — компонент, устанавливающий значение.
  • current — свойство для чтения значения в текущем контексте.

Хранение значений

Значения хранятся в композиционном контексте — объекте CompositionContext, который привязан к каждой позиции в дереве композиции. Технически, это реализовано как связанный список узлов:

// Упрощенная структура узла контекста
internal class CompositionLocalContext(
    val parent: CompositionLocalContext?,
    val values: Map<CompositionLocal<Any>, State<Any>?>
)

Когда вы устанавливаете значение через CompositionLocalProvider, создается новый узел контекста, который:

  1. Ссылается на родительский контекст
  2. Содержит обновленные значения для указанных CompositionLocal
  3. Не копирует все значения, только изменяемые

Различия staticCompositionLocalOf и compositionLocalOf

staticCompositionLocalOf

val StaticLocal = staticCompositionLocalOf { "default" }
// Под капотом:
// - Значение отслеживается на уровне всей рекомпозиции
// - Любое изменение вызывает рекомпозицию ВСЕХ читателей
// - Эффективно для редко меняющихся значений (например, конфигурация)

compositionLocalOf

val DynamicLocal = compositionLocalOf { "default" }
// Под капотом:
// - Использует систему отслеживания изменений Compose
// - Рекомпозируются только читатели в поддереве, где значение изменилось
// - Более интеллектуальное отслеживание зависимостей

Механизм чтения значений

Когда вы вызываете CompositionLocal.current, происходит:

// Упрощенный процесс поиска значения
fun <T> getCurrentValue(local: CompositionLocal<T>): T {
    var context = currentCompositionContext
    while (context != null) {
        context.values[local]?.let { return it.value }
        context = context.parent
    }
    return local.defaultValue
}

Процесс представляет собой подъем по цепочке контекстов до тех пор, пока не будет найдено значение. Если значение не найдено, возвращается значение по умолчанию.

Интеграция с системой рекомпозиции

  1. Регистрация зависимостей: Когда Composable читает CompositionLocal.current, система автоматически регистрирует зависимость от этого значения.

  2. Отслеживание изменений:

    • Для compositionLocalOf: используется Snapshot, аналогично mutableStateOf
    • Для staticCompositionLocalOf: изменения обнаруживаются через общий механизм рекомпозиции
  3. Оптимизация: Compose использует стабильность значений для минимизации ненужных рекомпозиций.

Производительность и оптимизации

CompositionLocal использует несколько оптимизаций:

  • Кэширование индексов: Каждому CompositionLocal присваивается уникальный индекс для быстрого доступа
  • Пропуск обновлений: Если новое значение равно старому (по equals), рекомпозиция не запускается
  • Ленивые вычисления: Значения вычисляются только при первом обращении

Пример внутренней реализации

// Упрощенная версия CompositionLocalProvider
@Composable
fun CompositionLocalProvider(
    context: CompositionLocalContext,
    content: @Composable () -> Unit
) {
    val currentContext = remember { CurrentCompositionLocalContext }
    val newContext = context.combine(currentContext)
    
    // Создание новой ячейки композиции с обновленным контекстом
    Composer.startProvider(newContext)
    content()
    Composer.endProvider()
}

Ключевые моменты реализации

  1. Иммутабельность: Контексты CompositionLocal неизменяемы, изменения создают новые контексты
  2. Наследование: Контексты образуют иерархию, похожую на DOM-дерево
  3. Согласованность: Значения гарантированно согласованы в пределах одной рекомпозиции
  4. Потокобезопасность: Механизм работает корректно в многопоточных сценариях благодаря системе снимков (Snapshot)

CompositionLocal представляет собой элегантное решение для передачи данных через дерево композиции, которое балансирует между удобством использования и производительностью, используя продвинутые механизмы отслеживания зависимостей Jetpack Compose.