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

Почему произойдет рекомпозиция если в Composable функцию передается List и его значение не изменилось?

2.0 Middle🔥 191 комментариев
#UI и вёрстка#Производительность и оптимизация

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

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

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

Почему происходит рекомпозиция при передаче неизмененного List в Composable функцию

В Jetpack Compose рекомпозиция — это процесс перевызова Composable функций для обновления UI при изменении состояния. При передаче коллекции, например List, даже если её содержимое не изменилось, рекомпозиция может произойти из-за особенностей работы системы отслеживания изменений (snapshot system) Compose и механизма сравнения параметров.

Ключевые причины рекомпозиции

1. Разные объектные ссылки Compose не сравнивает глубокое содержимое коллекций. Он отслеживает изменения через Snapshot систему, которая наблюдает за мутациями (изменениями) объектов. Если вы создаете новый объект List (например, через listOf() или преобразования), даже с идентичным содержимым, это новый объект в памяти с новой ссылкой. Compose воспринимает это как изменение параметра.

@Composable
fun MyList(items: List<String>) {
    // Рекомпозиция произойдет при каждом вызове с новым объектом List
    Text("Count: ${items.size}")
}

// В родительской функции:
val originalList = listOf("A", "B", "C")
var currentList = originalList

// При изменении ссылки (не содержимого!) - рекомпозиция
currentList = originalList + emptyList() // Новый объект List
MyList(currentList)

2. Особенности стабильных и нестабильных типов Compose классифицирует типы параметров как стабильные (stable) или нестабильные (unstable). Стабильный тип гарантирует, что Compose может определить его изменение без рекомпозиции. List (как интерфейс) считается нестабильным типом, потому что:

  • Его реализация (например, ArrayList) может мутировать без изменения ссылки.
  • Compose не может автоматически отследить глубокие изменения содержимого.

Это приводит к "консервативной" рекомпозиции: Compose предполагает изменение, если параметр нестабилен.

3. Параметр items не объявлен как stable Чтобы избежать лишних рекомпозиций, можно использовать стабильные коллекции или аннотировать классы:

@Stable
class StableItemList(val items: List<Item>) {
    // Стабильный класс с глубоким сравнением
}

@Composable
fun MyStableList(list: StableItemList) {
    // Рекомпозиция только при реальном изменении items
}

4. Неиспользование remember или производных состояний Если список вычисляется внутри Composable функции без сохранения, он создается заново при каждой рекомпозиции родителя:

@Composable
fun ParentComponent() {
    val items = calculateList() // Новый List при каждой рекомпозиции ParentComponent
    ChildComponent(items) // ChildComponent будет рекомпозироваться всегда
}

Решение — использовать remember для сохранения объекта или derivedStateOf для отслеживания изменений:

@Composable
fun ParentComponent() {
    val items = remember { calculateList() } // Объект сохраняется между рекомпозициями
    val filteredItems = remember(items) { items.filter { it.isActive } } // Пересчет только при изменении items
    ChildComponent(filteredItems)
}

Как предотвратить лишние рекомпозиции

  • Используйте remember для сохранения коллекций между рекомпозициями.
  • Применяйте derivedStateOf для преобразований списков, зависящих от состояния.
  • Оптимизируйте с помощью key или LaunchedEffect для управления рекомпозициями в ленивых списках (LazyColumn).
  • Рассмотрите стабильные типы через аннотации или использование ImmutableList (из коллекций Kotlin).
  • Используйте equals глубокого сравнения в классах-моделях (переопределяйте equals()/hashCode()).

Итог: рекомпозиция при передаче неизмененного List происходит из-за создания нового объекта ссылки и нестабильности типа. Compose перевызывает функцию для гарантии корректности UI, но это можно оптимизировать через механизмы управления состоянием.