Recomposition مکانیسمی در Jetpack Compose است که به طور خودکار بخشهایی از رابط کاربری را هنگام تغییر دادهها، بدون بهروزرسانی دستی عناصر View، بازسازی میکند. هنگامی که متغیر وضعیتی که یک تابع Composable به آن وابسته است تغییر میکند، Compose تنها آن تابع را دوباره اجرا میکند و بقیه درخت UI را دست نخورده باقی میگذارد. طبق دادههای Google Android Developers, 2026، درک صحیح Recomposition امکان کاهش تعداد بازترسیمهای اضافی را تا ۴۰–۶۰٪ فراهم میکند.
نکات اصلی
Recomposition اجرای مجدد توابع Composable است که قبلاً در Composition شرکت کردهاند، با مقادیر جدید پارامترها یا وضعیت. هدف اصلی ترکیب مجدد همگامسازی درخت UI با دادههای جاری بدون بازسازی کل رابط از صفر است. برخلاف Composition که یک بار انجام میشود، Recomposition میتواند صدها بار در طول عمر یک صفحه اجرا شود.
Recomposition بر اساس اصل smart invalidation کار میکند: Compose ردیابی میکند که هر تابع Composable کدام اشیاء State را میخواند و تنها آنهایی را که وابستگیهایشان تغییر کرده است برای اجرای مجدد علامتگذاری میکند. این امر از طریق سیستم snapshot حاصل میشود که تمام عملیات خواندن State را در طول اجرا ثبت میکند و Composer این وابستگیها را به توابع خاص نگاشت میکند.
درک این نکته مهم است: ترکیب مجدد به معنای بازترسیم فوری صفحه نیست. Compose در سه فاز کار میکند: Composition (ساخت توصیف UI)، Layout (محاسبه اندازهها و موقعیتها) و Drawing (رسم روی بوم). اگر پس از ترکیب مجدد اندازهها و موقعیت عناصر تغییر نکرده باشد، فاز Layout قابل رد شدن است. اگر ظاهر تغییر نکرده باشد — Drawing رد میشود. این معماری سه فازی حداقل هزینه را برای هر بهروزرسانی UI تضمین میکند.
سه محرک اصلی برای ترکیب مجدد وجود دارد. اولین — تغییر شیء State خوانده شده در بدنه تابع Composable. هنگامی که mutableStateOf یا derivedStateOf مقدار خود را تغییر میدهد، تمام توابعی که خواندن این State را در ترکیب قبلی ثبت کردهاند برای اجرای مجدد علامتگذاری میشوند.
دومین محرک — تغییر پارامترها تابع Composable هنگام فراخوانی از تابع والد. اگر تابع والد مقدار جدیدی ارسال کند (مثلاً متن یا عدد تغییر کرده باشد)، تابع فرزند دوباره اجرا میشود، حتی اگر State را درون خود نخواند. Compose مقادیر جدید و قدیم پارامترها را از طریق equals مقایسه میکند و اگر برابر باشند — تابع قابل رد شدن است.
سومین محرک — تغییر CompositionLocal از طریق CompositionLocalProvider. تمام توابعی که CompositionLocal را از طریق .current میخوانند، هنگام تغییر provider دوباره اجرا میشوند. این مکانیسم توسط MaterialTheme استفاده میشود: تغییر تم (روشن/تاریک) باعث ترکیب مجدد تمام مؤلفههایی میشود که MaterialTheme.colorScheme را میخوانند.
@Composable
fun RecompositionDemo() {
var counter by remember { mutableStateOf(0) }
var text by remember { mutableStateOf("Hello") }
Column {
Text("شمارنده: $counter") // ترکیب مجدد هنگام تغییر شمارنده
Text("پیام: $text") // ترکیب مجدد هنگام تغییر متن
Button(onClick = { counter++ }) {
Text("+1")
}
Button(onClick = { text = "جهان" }) {
Text("تغییر متن")
}
}
}
فشار دادن دکمه +1 counter را تغییر میدهد که فقط باعث ترکیب مجدد اولین خط Text و خود Column میشود. خط دوم Text که text را نمایش میدهد دوباره اجرا نمیشود. چنین ایزولهای نتیجه سیستم snapshot است: هر تابع Composable فقط از آن اشیاء State که خوانده است اطلاع دارد.
بهینهسازی ترکیب مجدد با انتخاب صحیح ساختارهای داده آغاز میشود. از مجموعههای تغییرناپذیر (listOf, mapOf) به جای مجموعههای تغییرپذیر (mutableListOf) استفاده کنید. Compose پارامترها را با equals مقایسه میکند و اگر مجموعه تغییر کرده باشد اما equals true برگرداند — تابع دوباره اجرا نمیشود. برای مجموعههای تغییرپذیر از SnapshotStateList استفاده کنید که ردیابی صحیح تغییرات را در سطح عناصر پیادهسازی میکند.
دومین تکنیک — جداسازی بخشهای پایدار UI به توابع Composable جداگانه. اگر بخشی از صفحه به وضعیت مکرراً متغیر وابسته نیست، آن را به یک تابع جداگانه با پارامترها استخراج کنید. وقتی ترکیب مجدد رخ میدهد، تابع پایدار همان پارامترها را دریافت میکند، Compose آنها را مقایسه کرده و اجرا را رد میکند. این کار مقرونبهصرفهتر از اجرای این بخش درون یک تابع بزرگ است که بخشی از پارامترهایش تغییر کرده است.
سومین تکنیک — کلیدها در LazyColumn. همیشه برای آیتم در LazyColumn، LazyGrid و سایر کانتینرهای تنبل key مشخص کنید. کلید به Compose اجازه میدهد عناصر را هنگام تغییر لیست شناسایی کند: افزودن، حذف یا جابجایی. بدون کلید، Compose تمام عناصر لیست را در هر تغییری دوباره اجرا میکند که در لیستهای بزرگ افت محسوس عملکرد ایجاد میکند.
// ساختار بهینهشده: بخشهای پایدار جداگانه استخراج شدهاند
@Composable
fun OptimizedScreen(items: List<Item>) {
Column {
Header() // به عناصر وابسته نیست — ترکیب مجدد انجام نمیشود
Spacer(modifier = Modifier.height(8.dp))
LazyColumn {
items(items, key = { it.id }) { item ->
ItemRow(item = item) // ترکیب مجدد فقط برای عناصر تغییر یافته
}
}
}
}
@Composable
fun Header() {
Text("فهرست عناصر", style = MaterialTheme.typography.headlineMedium)
}
@Composable
fun ItemRow(item: Item) {
Text(item.title)
}
Skipping مکانیسمی است که در آن Compose اجرای تابع Composable را رد میکند اگر همه پارامترهایش تغییر نکرده باشند. برای کارکرد صحیح skipping، انواع پارامترها باید پایدار (stable) باشند. کامپایلر Kotlin موارد زیر را به عنوان پایدار علامتگذاری میکند: انواع اولیه (Int, Float, Boolean)، String، توابع لامبدا، همچنین کلاسهایی که همه فیلدهایشان پایدار و val هستند.
Stability حاشیهنویسی @Stable یا @Immutable است که میتوان به کلاسهای داده سفارشی اضافه کرد. اگر کلاس حاوی فیلد تغییرپذیر (var) باشد، کامپایلر آن را ناپایدار در نظر میگیرد و Compose نمیتواند از توابع با چنین پارامترهایی رد شود. برای کلاسهای دارای var از @Stable استفاده کنید اگر تضمین میدهید که اعلان تغییر از طریق سیستم snapshot ارسال میشود.
میتوان stability را از طریق پرچم کامپایلر بررسی کرد: -P "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=. /reports". این کار گزارشی با فهرست همه توابع Composable و پارامترهایشان با نشاندهنده stability تولید میکند. اگر پارامتر ناپایدار باشد — skipping برای آن تابع غیرممکن است و در هر ترکیب مجدد والد دوباره اجرا میشود.
| نوع | پایداری | Skipping |
|---|---|---|
| Int, Float, Boolean | پایدار | بله |
| String | پایدار | بله |
| Lambda | پایدار | بله |
| data class با فیلدهای val | پایدار | بله |
| data class با فیلدهای var | ناپایدار | خیر |
| List<String> | ناپایدار | خیر |
توجه کنید: List<String> ناپایدار در نظر گرفته میشود زیرا این یک interface است، نه یک پیادهسازی مشخص. از immutableListOf() از کتابخانه Kotlin Collections Immutable استفاده کنید یا لیست را در یک کلاس @Stable بپیچید. Lambda همیشه پایدار است زیرا equals آن فقط مراجع را مقایسه میکند و هنگام ایجاد لامبدای جدید در محل فراخوانی، تابع والد نیز دوباره اجرا میشود.
برای نظارت بر ترکیبهای مجدد، Android Studio Layout Inspector را با حالت Compose Recomposition Counts ارائه میدهد. در این حالت، هر تابع Composable تعداد ترکیبهای مجدد و دلایل اجرای مجدد را نمایش میدهد. این امکان را فراهم میکند تا توابعی که بیش از حد مکرر ترکیب مجدد میشوند را پیدا کنید و علت ریشهای را تعیین کنید — پارامترهای ناپایدار یا وابستگیهای State اضافی.
ابزارهای اضافی: Compose Metrics (جمعآوری آمار از طریق تستهای instrumentation) و Recomposition Timer (اندازهگیری زمان اجرای هر تابع). گوگل توصیه میکند این ابزارها را در مرحله پروفایلینگ فعال کرده و در نسخههای release غیرفعال کنید، زیرا تا ۲۰٪ سربار به هر ترکیب مجدد اضافه میکنند.
هنگام تحلیل ترکیبهای مجدد به دنبال الگوهای unnecessary recomposition بگردید: تابع دوباره اجرا میشود در حالی که خروجی UI آن نباید تغییر کند. علت مکرر استفاده از لامبداها بدون remember است، زمانی که هر بار یک شیء لامبدای جدید ایجاد میشود و Compose پارامتر را تغییر یافته در نظر میگیرد. راهحل: پیچیدن لامبداها در remember { } با clutchedهای ثابت.
// بد: لامبدای جدید در هر ترکیب مجدد والد
@Composable
fun Parent() {
Child(onClick = { doSomething() }) // هر بار لامبدای جدید
}
// خوب: remember لامبدا را پایدار میکند
@Composable
fun Parent() {
val onClick = remember { { doSomething() } }
Child(onClick = onClick) // مرجع یکسان
}
سوالات متداول
خیر، ترکیب مجدد فقط فاز Composition است. پس از آن Layout و Drawing اجرا میشوند. اگر پس از ترکیب مجدد اندازهها و موقعیت عناصر تغییر نکرده باشد، Layout و Drawing میتوانند کاملاً رد شوند که باعث صرفهجویی در منابع GPU میشود.
در انیمیشنها، ترکیب مجدد میتواند تا ۱۲۰ بار در ثانیه (۱۲۰fps) اجرا شود. برای تعامل معمولی — ۱۰–۶۰ بار در ثانیه. مهم است که هر ترکیب مجدد در بودجه فریم (۸–۱۶ میلیثانیه) قرار گیرد، در غیر این صورت برنامه کند خواهد شد.
علت — تغییر پارامتر از تابع والد. والد (به دلیلی خود) دوباره اجرا میشود و مقدار جدیدی ارسال میکند. برای جلوگیری از این، stability پارامترها را بررسی کنید و از remember برای پایدارسازی لامبداها و مقادیر محاسبهشده استفاده کنید.
غیرفعالسازی مستقیم وجود ندارد، اما skipping اجباری از طریق readInComposition وجود دارد — State خارج از بدنه تابع خوانده میشود که وابستگی را ثبت نمیکند. از این با احتیاط استفاده کنید: تابع به تغییرات واکنش نشان نخواهد داد که میتواند به UI قدیمی منجر شود.
Composition گرانتر است زیرا تمام slotها و گرههای درخت را از صفر ایجاد میکند. Recomposition از slotهای موجود استفاده مجدد کرده و فقط مقادیر آنها را بهروز میکند. در عمل، Composition صفحه ۲–۱۰ میلیثانیه و ترکیب مجدد یک عنصر ۰.۱–۱ میلیثانیه طول میکشد.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید