Что такое 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 работает на двух уровнях:
- Выбор нужных scope (на базе State => Scope зависимостей)
- Внутри scope — то есть какие конкретно вызовы
@Composableможно проскочить (skip)
Как Compose понимает, какие composables перерисовать
В очень упрощённом виде:
- State изменился => Snapshot помечает его как modified.
- Snapshot при
applyговорит Recomposer’у: «вот список State, которые изменились». - Recomposer для каждого такого State берёт список Recomposition Scope, где он читался:
- помечает эти scope invalid.
- В следующем кадре (frame) Recomposer запускает recomposition:
- идёт по дереву групп (slot table),
- когда встречает invalid scope:
- вызывает связанную
@Composableснова - внутри неё:
- обновляет слоты
- для каждой вложенной composable решает:
restartилиskip
- вызывает связанную
- При
skipпросто двигает “курсор” по дереву, не выполняя код и переиспользуя старое поддерево.