Тяжёлая recomposition и стабильность
Разбираемся, почему UI начинает «тормозить», и как сузить область перерисовки.
Тяжёлая recomposition — ситуация, когда при изменении состояния перерисовывается слишком большая часть дерева или функции выполняются чаще, чем нужно.
Читайте состояние как можно ниже
Compose перерисовывает ту функцию, что читает изменившееся состояние. Если читать его высоко, перерисуется большой кусок. Сравните:
// Плохо: весь Screen читает count и перерисовывается целиком
@Composable
fun Screen(count: Int) {
Heavy() // тоже перерисуется без нужды
Text("Счёт: $count")
}
// Лучше: чтение состояния спрятано в маленький Counter
@Composable
fun Screen() {
Heavy() // не зависит от count, не перерисуется
Counter() // читает count внутри себя
}Чем ниже и локальнее чтение состояния, тем уже область recomposition.
Стабильность параметров и пропуск
Compose может пропустить вызов composable, если его параметры не изменились. Но для этого типы параметров должны быть стабильными: их равенство предсказуемо. Неизменяемые data class с val-полями и примитивы стабильны. А вот изменяемый List или лямбда, создаваемая заново на каждой перерисовке, могут срывать пропуск.
derivedStateOf для производных значений
Если значение вычисляется из другого состояния, но меняется реже, derivedStateOf избавит от лишних перерисовок:
val showButton by remember {
derivedStateOf { listState.firstVisibleItemIndex > 0 }
}Здесь showButton пересчитывается при каждом скролле, но меняется (true/false) редко — и UI перерисуется лишь в моменты реальной смены, а не на каждый пиксель прокрутки.
Как работает под капотом
Для каждого пропускаемого composable Compose сравнивает новые параметры со старыми через equals. Если тип помечен как стабильный и значения равны — вызов пропускается. Нестабильный тип Compose вынужден считать «возможно изменившимся» и перевыполнять функцию на всякий случай. Поэтому стабильные неизменяемые модели данных напрямую влияют на скорость UI.
Частые ошибки
- Читать состояние в самом верху экрана, заставляя перерисовываться всё дерево.
- Передавать в composable изменяемые коллекции и пересоздаваемые лямбды, ломая пропуск.
- Делать тяжёлые вычисления прямо в теле composable вместо
remember/derivedStateOf.
Итог
- Читайте состояние как можно ниже, чтобы сузить область перерисовки.
- Стабильные параметры позволяют Compose пропускать неизменившиеся composable.
derivedStateOfиrememberспасают от лишних перерисовок и пересчётов.