Recomposition — چیست، بازسازی رابط کاربری هنگام تغییر وضعیت

نویسنده: IT Sectr منتشر شده: 2026-06-27 زمان مطالعه: 7 دقیقه

Recomposition مکانیسمی در Jetpack Compose است که به طور خودکار بخش‌هایی از رابط کاربری را هنگام تغییر داده‌ها، بدون به‌روزرسانی دستی عناصر View، بازسازی می‌کند. هنگامی که متغیر وضعیتی که یک تابع Composable به آن وابسته است تغییر می‌کند، Compose تنها آن تابع را دوباره اجرا می‌کند و بقیه درخت UI را دست نخورده باقی می‌گذارد. طبق داده‌های Google Android Developers, 2026، درک صحیح Recomposition امکان کاهش تعداد بازترسیم‌های اضافی را تا ۴۰–۶۰٪ فراهم می‌کند.

نکات اصلی

  • Recomposition — اجرای مجدد توابع Composable هنگام تغییر ورودی‌ها یا State
  • Skipping — رد شدن از توابعی که پارامترهایشان تغییر نکرده است (مقایسه با equals)
  • Stability تعیین می‌کند که آیا Compose می‌تواند از تابعی رد شود — انواع پایدار به درستی مقایسه می‌شوند
  • Smart Recomposition فقط حداقل مجموعه توابع را دوباره اجرا می‌کند، نه کل درخت
  • ترکیب مجدد تضمین‌کننده بازترسیم نیست — Layout و Drawing ممکن است فاز را رد کنند

Recomposition در Jetpack Compose چیست

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 را می‌خوانند.

kotlin
@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 تمام عناصر لیست را در هر تغییری دوباره اجرا می‌کند که در لیست‌های بزرگ افت محسوس عملکرد ایجاد می‌کند.

kotlin
// ساختار بهینه‌شده: بخش‌های پایدار جداگانه استخراج شده‌اند
@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 و Stability در Compose

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

برای نظارت بر ترکیب‌های مجدد، Android Studio Layout Inspector را با حالت Compose Recomposition Counts ارائه می‌دهد. در این حالت، هر تابع Composable تعداد ترکیب‌های مجدد و دلایل اجرای مجدد را نمایش می‌دهد. این امکان را فراهم می‌کند تا توابعی که بیش از حد مکرر ترکیب مجدد می‌شوند را پیدا کنید و علت ریشه‌ای را تعیین کنید — پارامترهای ناپایدار یا وابستگی‌های State اضافی.

ابزارهای اضافی: Compose Metrics (جمع‌آوری آمار از طریق تست‌های instrumentation) و Recomposition Timer (اندازه‌گیری زمان اجرای هر تابع). گوگل توصیه می‌کند این ابزارها را در مرحله پروفایلینگ فعال کرده و در نسخه‌های release غیرفعال کنید، زیرا تا ۲۰٪ سربار به هر ترکیب مجدد اضافه می‌کنند.

هنگام تحلیل ترکیب‌های مجدد به دنبال الگوهای unnecessary recomposition بگردید: تابع دوباره اجرا می‌شود در حالی که خروجی UI آن نباید تغییر کند. علت مکرر استفاده از لامبداها بدون remember است، زمانی که هر بار یک شیء لامبدای جدید ایجاد می‌شود و Compose پارامتر را تغییر یافته در نظر می‌گیرد. راه‌حل: پیچیدن لامبداها در remember { } با clutchedهای ثابت.

kotlin
// بد: لامبدای جدید در هر ترکیب مجدد والد
@Composable
fun Parent() {
    Child(onClick = { doSomething() })  // هر بار لامبدای جدید
}

// خوب: remember لامبدا را پایدار می‌کند
@Composable
fun Parent() {
    val onClick = remember { { doSomething() } }
    Child(onClick = onClick)  // مرجع یکسان
}

سوالات متداول

آیا ترکیب مجدد به معنای بازترسیم صفحه است؟

خیر، ترکیب مجدد فقط فاز Composition است. پس از آن Layout و Drawing اجرا می‌شوند. اگر پس از ترکیب مجدد اندازه‌ها و موقعیت عناصر تغییر نکرده باشد، Layout و Drawing می‌توانند کاملاً رد شوند که باعث صرفه‌جویی در منابع GPU می‌شود.

چقدر ممکن است ترکیب مجدد رخ دهد؟

در انیمیشن‌ها، ترکیب مجدد می‌تواند تا ۱۲۰ بار در ثانیه (۱۲۰fps) اجرا شود. برای تعامل معمولی — ۱۰–۶۰ بار در ثانیه. مهم است که هر ترکیب مجدد در بودجه فریم (۸–۱۶ میلی‌ثانیه) قرار گیرد، در غیر این صورت برنامه کند خواهد شد.

چرا تابع ترکیب مجدد می‌شود در حالی که State تغییر نکرده است؟

علت — تغییر پارامتر از تابع والد. والد (به دلیلی خود) دوباره اجرا می‌شود و مقدار جدیدی ارسال می‌کند. برای جلوگیری از این، stability پارامترها را بررسی کنید و از remember برای پایدارسازی لامبداها و مقادیر محاسبه‌شده استفاده کنید.

آیا می‌توان ترکیب مجدد را برای یک تابع خاص غیرفعال کرد؟

غیرفعال‌سازی مستقیم وجود ندارد، اما skipping اجباری از طریق readInComposition وجود دارد — State خارج از بدنه تابع خوانده می‌شود که وابستگی را ثبت نمی‌کند. از این با احتیاط استفاده کنید: تابع به تغییرات واکنش نشان نخواهد داد که می‌تواند به UI قدیمی منجر شود.

کدام گران‌تر است: Composition یا Recomposition؟

Composition گران‌تر است زیرا تمام slotها و گره‌های درخت را از صفر ایجاد می‌کند. Recomposition از slotهای موجود استفاده مجدد کرده و فقط مقادیر آن‌ها را به‌روز می‌کند. در عمل، Composition صفحه ۲–۱۰ میلی‌ثانیه و ترکیب مجدد یک عنصر ۰.۱–۱ میلی‌ثانیه طول می‌کشد.

خلاصه

  • Recomposition — اجرای مجدد انتخابی توابع Composable هنگام تغییر State یا پارامترهایشان
  • Snapshot system وابستگی‌های توابع به State را ردیابی کرده و ترکیب مجدد را برنامه‌ریزی می‌کند
  • Skipping فقط برای توابع با پارامترهای پایدار امکان‌پذیر است (@Stable یا immutable)
  • سه محرک ترکیب مجدد: تغییر State، تغییر پارامترها، تغییر CompositionLocal
  • List<T> ناپایدار در نظر گرفته می‌شود — برای skipping صحیح از مجموعه‌های تغییرناپذیر استفاده کنید
  • Layout Inspector شمارنده‌های ترکیب مجدد را برای هر تابع نشان می‌دهد
  • توصیه: بخش‌های پایدار UI را به توابع جداگانه استخراج کرده و برای لامبداها از remember استفاده کنید

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید