← Назад к вопросам
Как Jetpack Compose понимает что нужно выполнить рекомпозицию
2.0 Middle🔥 151 комментариев
#UI и вёрстка#Архитектура и паттерны
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Механизм Recomposition в Jetpack Compose
Рекомпозиция - это процесс перерисовки UI когда изменилось состояние. Это ключ к пониманию Compose.
Основной механизм: State Tracking
Compose отслеживает состояние и его изменения:
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) } // Состояние
Column {
Text("Count: $count") // Зависит от count
Button(onClick = { count++ }) { // Изменит count
Text("Increment")
}
}
}
// Когда count изменилась, Compose автоматически вызывает рекомпозицию
Как Compose отслеживает изменения
1. mutableStateOf + remember
var state by remember { mutableStateOf(initialValue) }
// Эквивалент (без remember):
var state by mutableStateOf(initialValue) // Потеряется при рекомпозиции!
// remember гарантирует сохранение состояния
Внутренний механизм:
- mutableStateOf создает State объект
- remember кеширует этот объект
- При изменении State, подписчики уведомляются
- Compose перезапускает composable функцию
State объект и подписка
// Под капотом
class MutableState<T>(private var _value: T) : State<T> {
private val subscribers = mutableSetOf<(T) -> Unit>()
override var value: T
get() = _value
set(newValue) {
if (_value != newValue) {
_value = newValue
subscribers.forEach { it(newValue) } // Уведомить
}
}
}
// Compose подписывается при использовании в composable
@Composable
fun MyComposable(state: State<String>) {
val value = state.value // Compose отмечает зависимость
Text(value) // Зависит от value
}
Snapshot System: Отслеживание изменений
Compose использует Snapshot для отслеживания:
// Для каждой рекомпозиции создается snapshot
val currentSnapshot = Snapshot.current()
var name by remember { mutableStateOf("John") }
var age by remember { mutableStateOf(30) }
// Snapshot отслеживает, какие State были прочитаны
// Если прочитанный State изменился → рекомпозиция
Когда происходит рекомпозиция
1. Изменение State, который используется в composable
@Composable
fun MyScreen() {
var counter by remember { mutableStateOf(0) }
// Эта ветка использует counter
Text("Counter: $counter") // Зависит от counter
Button(onClick = { counter++ }) {
Text("Increment")
}
// counter++ → Text перерисуется
}
2. Изменение параметра composable
@Composable
fun Parent() {
var name by remember { mutableStateOf("John") }
Child(name) // Передали name
Button(onClick = { name = "Jane" }) {
Text("Change")
}
}
@Composable
fun Child(name: String) {
Text(name) // Зависит от параметра
}
// name изменилась → Child перерисуется
Что НЕ вызывает рекомпозицию
@Composable
fun Example() {
// ПЛОХО: просто переменная, не State
var counter = 0
Button(onClick = { counter++ }) { // Увеличит локальную переменную
Text("Counter: $counter") // Но не вызовет рекомпозицию!
}
// Текст всегда показывает 0!
}
// ПРАВИЛЬНО:
@Composable
fun Example() {
var counter by remember { mutableStateOf(0) }
Button(onClick = { counter++ }) { // Изменит State
Text("Counter: $counter") // Вызовет рекомпозицию
}
}
Ветвление и рекомпозиция
@Composable
fun Greeting(name: String) {
var showDetails by remember { mutableStateOf(false) }
Text("Hello $name")
if (showDetails) { // Условное выполнение
Text("Details...")
}
Button(onClick = { showDetails = !showDetails }) {
Text("Toggle")
}
}
// showDetails = false → "Details" не в дереве
// showDetails = true → "Details" появляется
// Compose добавляет/удаляет элементы из дерева
Оптимизация: derivedStateOf
@Composable
fun SearchResults(query: String, results: List<String>) {
var selectedIndex by remember { mutableStateOf(0) }
// ПЛОХО: пересчитывается при каждой рекомпозиции
val filteredResults = results.filter { it.contains(query) }
// ХОРОШО: пересчитывается только если query или results изменились
val filteredResults by remember(query, results) {
derivedStateOf { results.filter { it.contains(query) } }
}
LazyColumn {
items(filteredResults) { result ->
Text(result)
}
}
}
skipDefaultParamComparison
@Composable
fun User(user: User) {
Text(user.name) // Если user остается тем же объектом → no recomposition
}
// ПРОБЛЕМА: новый объект User каждый раз
@Composable
fun Parent() {
val user = User(name = "John") // Новый объект каждый раз!
User(user) // Всегда рекомпозиция
}
// РЕШЕНИЕ: мемоизация
@Composable
fun Parent() {
val user = remember { User(name = "John") } // Один объект
User(user) // Рекомпозиция только если user.name изменилась
}
Scope рекомпозиции
@Composable
fun Parent() {
var parentCounter by remember { mutableStateOf(0) }
var childCounter by remember { mutableStateOf(0) }
Text("Parent: $parentCounter") // Зависит от parentCounter
Child(childCounter) { newValue ->
childCounter = newValue
}
Button(onClick = { parentCounter++ }) {}
}
@Composable
fun Child(counter: Int, onUpdate: (Int) -> Unit) {
Text("Child: $counter")
Button(onClick = { onUpdate(counter + 1) }) {}
}
// parentCounter++ → Parent перерисуется
// → Child получит новое значение
// → Child перерисуется
Инструменты отладки
LayoutInspector - показывает дерево composable
Compose Metrics - анализирует рекомпозиции
RecomposeHighlighter - подсвечивает перерисовки
Лучшие практики
✅ Делай так:
- Используй remember для сохранения состояния
- Используй remember(key) для зависимостей
- Используй derivedStateOf для вычислений
- Мемоизируй вычисляемые значения
- Разделяй крупные composable на меньше
❌ Избегай:
- Создания новых объектов без remember
- Простых переменных вместо State
- Ненужных рекомпозиций
- Сложной логики в composable
Вывод
Compose отслеживает рекомпозицию через:
- State Tracking - отслеживание изменений mutableStateOf
- Snapshot System - регистрация прочитанных State
- Dependency Graph - какой composable зависит от какого State
- Scope - перезапуск только затронутых composable
Этот механизм позволяет эффективно обновлять UI при изменении состояния.