Compose сам решает, какие composable можно пропустить при рекомпозиции: если функция skippable и все её параметры не изменились, повторного вызова не происходит. Раньше правило ломалось на параметрах, которые компилятор не мог считать stable, — LocalDateTime, List вместо ImmutableList, любой класс из чужого модуля без @Stable. Один такой параметр блокировал skip и каскадно дёргал рекомпозицию вглубь.
Команды реагировали аннотациями @Stable и @Immutable на каждом доменном классе, обёртками над списками и remember на каждой лямбде. Получался шум, который сложно сопровождать и легко поставить неправильно — @Stable на классе с фактически мутабельным состоянием создаёт скрытые баги.
Strong skipping (Compose Compiler 1.5.4+, по умолчанию в Compose 1.7) изменил два правила.
Первое: unstable-параметры сравниваются по instance equality (===) вместо немедленного отказа от skip. Если ViewModel вернул тот же экземпляр Order, composable пропускается, даже если класс формально нестабилен.
fun OrderRow(order: Order, onTap: () -> Unit) { ... }
// раньше: unstable Order → всегда рекомпозиция
// теперь: skip, если order === предыдущему order
Второе важнее: лямбды с unstable-захватами компилятор оборачивает в remember автоматически.
val onTap = remember(order.id) { { viewModel.markPaid(order.id) } }
// со strong skipping эквивалент пишется компилятором:
val onTap = { viewModel.markPaid(order.id) }
// → разворачивается в remember(viewModel, order.id) { { ... } }
Из практического: большую часть @Stable и @Immutable на доменных классах можно убирать. Они не вредят, но добавляют шум в коде и в дифах.
Три места, где нужно остановиться:
они живут вне @Composable. Если контент тяжёлый, оборачивайте руками.
LazyColumn {
items(orders, key = { it.id }) { order ->
OrderRow(order, onTap = remember(order.id) { { ... } })
}
}
2. @Stable нужен, если ViewModel выдаёт НОВЫЕ инстансы
с теми же данными -- типичный случай DTO → UI-model маппинга
@Stable
data class OrderUi(val id: String, val total: Money)
3. enum и sealed class инферятся stable без аннотации -- она не нужна
Включение, если ещё не на 1.7+ :
composeCompiler {
enableStrongSkippingMode.set(true)
}
Эффект меряйте через Layout Inspector с включёнными recomposition counts или composition tracing. Без замеров легко поверить, что стало быстрее, тогда как часть scope всё ещё перерисовывается на каждой эмиссии состояния. И отдельно — key в LazyColumn обязателен, без него счётчики рекомпозиции показывают завышенные значения независимо от skipping-режима.
#mobilevkhub #compose #strongskipping