Что такое Recomposition Scope и Smart Recomposition. Как Compose понимает, какие composables перерисовать?

Что такое Recomposition Scope

Если вкратце, то Recomposition Scope — это кусок дерева композиции, который может быть перекомпозирован отдельно от остальных.

На практике это обычно тело одной @Composable функции, тело лямбды (content = { ... }), группа под key { ... }, remember {}, LaunchedEffect {}, и т.п.

Внутри рантайма Composer строит дерево групп (composition groups).

Каждая такая группа есть scope, который можно отдельно инвалидировать (пометить «нужно пересчитать»), отдельно перезапустить (recompose), при этом не трогать соседние группы.

@Composable
fun Screen() {
    Column {
        Header()        // scope A
        Content()       // scope B
        Footer()        // scope C
    }
}
  • Screen — тоже scope, но внутри него ещё три “дочерних” scope.
  • Если стейт читали только в Content(), при изменении стейта Compose постарается перерисовать только scope B, оставив A и C нетронутыми.

Внутри Composer у каждого scope есть ID/индекс и связка «какие State читались в этом scope».

Как Compose связывает State и Recomposition Scope

Каждый State знает, в каких scope его читали, а каждый scope знает, какие State-ы он прочитал.

Пример:

@Composable
fun Content() {
    val count by remember { mutableStateOf(0) }
    Text("$count")
}

При чтении count snapshot-система помечает: «State<count> был прочитан в scope Content«

Когда делаем count++: MutableState помечается как изменённый, snapshot помечает: «изменился State<count>, его читали scope Content«. Далее, Recomposer планирует recomposition этих scope (а не всего дерева)

То есть:

State знает набор Recomposition Scope, где его читали.
Если изменился State то помечаются нужные scope как invalid и Recomposer проходится только по ним.

Что такое Smart Recomposition

Это набор оптимизаций, чтобы не перерисовывать всё дерево при каждом изменении и минимизировать количество реально выполняемых composable-функций.

Smart Recomposition работает на двух уровнях:

  1. Выбор нужных scope (на базе State => Scope зависимостей)
  2. Внутри scope — то есть какие конкретно вызовы @Composable можно проскочить (skip)

Как Compose понимает, какие composables перерисовать

В очень упрощённом виде:

  1. State изменился => Snapshot помечает его как modified.
  2. Snapshot при apply говорит Recomposer’у: «вот список State, которые изменились».
  3. Recomposer для каждого такого State берёт список Recomposition Scope, где он читался:
    • помечает эти scope invalid.
  4. В следующем кадре (frame) Recomposer запускает recomposition:
    • идёт по дереву групп (slot table),
    • когда встречает invalid scope:
      • вызывает связанную @Composable снова
      • внутри неё:
        • обновляет слоты
        • для каждой вложенной composable решает: restart или skip
  5. При skip просто двигает “курсор” по дереву, не выполняя код и переиспользуя старое поддерево.

Опубликовано

в

,

от

Метки: