Composition: ماهیت، ساخت درخت UI در Compose

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

Composition — فرآیند مرکزی در Jetpack Compose است که طی آن از توابع توصیفی Composable یک درخت UI زنده ساخته می‌شود که روی صفحه نمایش داده می‌شود. برخلاف سیستم View اندروید، که طرح‌بندی از XML بارگذاری و به اشیاء تغییرناپذیر تبدیل می‌شد، Composition به عنوان یک سیستم پویا کار می‌کند: توابع اجرا می‌شوند، slotهایی در حافظه ایجاد می‌کنند، سلسله‌مراتب گره‌ها را شکل می‌دهند و آن را به وضعیت متصل می‌کنند. به گفته Google Android Developers, 2026، درک Composition برای بهینه‌سازی عملکرد برنامه‌های Compose حیاتی است.

نکات کلیدی

  • Composition — اجرای توابع Composable برای ساخت درخت UI است
  • Slotها — سلول‌های حافظه‌ای که پارامترها و وضعیت هر تابع را ذخیره می‌کنند
  • موقعیت در Compose (Positional Memorization) وضعیت را به مکان در کد متصل می‌کند
  • گذر اول Composition درخت UI اولیه را هنگام راه‌اندازی صفحه ایجاد می‌کند
  • CompositionLocal داده‌ها را از طریق درخت بدون پارامترهای صریح منتقل می‌کند

Composition در Jetpack Compose چیست

Composition — فرآیند اجرای توابع Composable است که در نتیجه آن نمایش داخلی رابط کاربری به صورت درختی از گره‌ها شکل می‌گیرد. هر گره این درخت یا با یک کامپوننت داخلی (Text, Button, Image) یا با فراخوانی یک تابع Composable سفارشی مطابقت دارد. Composition مستقیماً اشیاء View اندروید را ایجاد نمی‌کند — بلکه یک توصیف انتزاعی می‌سازد که سپس توسط فازهای Layout و Drawing پردازش می‌شود.

ویژگی کلیدی Composition — قابلیت راه‌اندازی مجدد (restartability) آن است. هر تابع Composable در ترکیب می‌تواند در هر لحظه دوباره راه‌اندازی شود، اگر پارامترهای ورودی یا اشیاء وضعیت خوانده شده توسط آن تغییر کرده باشند. سیستم کل درخت را راه‌اندازی مجدد نمی‌کند — فقط توابعی که واقعاً به داده‌های تغییر یافته وابسته هستند.

از نظر فنی، Composition توسط Composer مدیریت می‌شود — موتور داخلی که کامپایلر Kotlin در هر تابع Composable جاسازی می‌کند. Composer در slotها (گروه‌های موقعیت) اطلاعاتی درباره اینکه کدام توابع با چه پارامترهایی و به چه ترتیبی فراخوانی شده‌اند، ثبت می‌کند. در فراخوانی‌های بعدی، Composer داده‌های جدید را با داده‌های ذخیره شده مقایسه کرده و درباره راه‌اندازی مجدد تصمیم می‌گیرد.

درخت UI در فرآیند Composition چگونه ساخته می‌شود

فرآیند ساخت درخت UI با فراخوانی متد setContent در داخل Activity یا Fragment آغاز می‌شود. این متد یک Composition اولیه ایجاد می‌کند و اجرای تابع Composable ریشه را شروع می‌کند. سپس هر تابع Composable تو در تو، گره‌های خود را به درخت اضافه کرده و سلسله‌مراتبی تشکیل می‌دهد: Row شامل Text و Button، Column شامل Image و Card و غیره.

هر گره درخت یک کلید موقعیت منحصر به فرد دریافت می‌کند که بر اساس موقعیت آن در کد منبع است. این کلید برای شناسایی گره در اجراهای بعدی استفاده می‌شود. کلید موقعیت دلیلی است که ترتیب فراخوانی توابع Composable نباید به شرایط بستگی داشته باشد: اگر در یک اجرا A -> B فراخوانی شده باشد و در اجرای بعدی B -> A، Compose قادر به تطبیق گره‌های قدیم و جدید نخواهد بود.

kotlin
@Composable
fun AppScreen() {
    Column {                     // گره Column (موقعیت 1)
        HeaderSection()            // گره HeaderSection (موقعیت 2)
        ContentSection()           // گره ContentSection (موقعیت 3)
        FooterSection()            // گره FooterSection (موقعیت 4)
    }
}

@Composable
fun HeaderSection() {
    Row {                       // گره Row (موقعیت 2.1)
        Text("عنوان")         // گره Text (موقعیت 2.2)
        Icon(...)                // گره Icon (موقعیت 2.3)
    }
}

در این مثال، هر فراخوانی موقعیتی مبتنی بر ترتیب در کد دریافت می‌کند. Column (موقعیت 1) شامل سه گره فرزند (موقعیت‌های 2، 3، 4) است. HeaderSection دو گره فرزند دیگر اضافه می‌کند (2.1، 2.2، 2.3). اگر در recomposition بعدی ContentSection قبل از HeaderSection فراخوانی شود، Composer قادر به تطبیق صحیح گره‌ها نخواهد بود — از این رو قانون: ترتیب فراخوانی توابع Composable باید پایدار باشد.

مدیریت وضعیت در Composition

وضعیت در Composition از طریق اشیاء از نوع State<T> مدیریت می‌شود. وقتی یک تابع Composable مقدار را از State از طریق خاصیت تفویض شده (by) می‌خواند، وابستگی به آن State را ثبت می‌کند. هنگام تغییر مقدار، تمام توابعی که این State را خوانده‌اند برای راه‌اندازی مجدد در فاز بعدی ترکیب علامت‌گذاری می‌شوند.

مکانیسم ثبت وابستگی‌ها سیستم snapshot نامیده می‌شود. هر بار که State تغییر می‌کند، snapshot تمام تغییرات را ثبت کرده و به Composer اطلاع می‌دهد که کدام توابع به این State وابسته هستند. درک این نکته مهم است: خواندن State در کد غیر Composable (مثلاً در لامبدای onClick) وابستگی را ثبت نمی‌کند — فقط خواندن داخل تابع Composable یا در لامبداهایی که در بافت ترکیب اجرا می‌شوند.

سیستم snapshot به صورت تراکنشی کار می‌کند: چندین تغییر State در یک رویداد در یک تراکنش ترکیب می‌شوند که از recompositionهای متعدد جلوگیری می‌کند. این امر به ویژه در پردازش ژست‌ها مهم است: در یک حرکت، چندین شیء State تغییر می‌کند، اما Compose فقط یک recomposition اجرا می‌کند.

kotlin
@Composable
fun StateExample() {
    var text by remember { mutableStateOf("Hello") }
    var isVisible by remember { mutableStateOf(true) }

    Column {
        Text(text)  // وابستگی به text را ثبت می‌کند

        if (isVisible) {  // وابستگی به isVisible را ثبت می‌کند
            TextField(value = text, onValueChange = { text = it })
        }

        Button(onClick = { isVisible = !isVisible }) {
            Text(if (isVisible) "مخفی کردن" else "نمایش")
        }
    }
}

تغییر text منجر به recomposition فقط Column، Text و TextField می‌شود. Button و شرط isVisible بدون تغییر می‌مانند. چنین ایزوله‌بودن recomposition مزیت کلیدی Compose نسبت به سیستم‌هایی است که کل صفحه را دوباره ترسیم می‌کنند. هر تابع Composable فقط آن اشیاء State را که مستقیماً می‌خواند، دنبال می‌کند.

Composition در مقابل Recomposition: تفاوت‌های کلیدی

Composition (ترکیب) و Recomposition (بازترکیب) — دو حالت مختلف اجرای توابع Composable هستند. Composition یک بار هنگام ایجاد صفحه رخ می‌دهد: سیستم تمام توابع Composable را با مقادیر اولیه اجرا کرده و درخت UI اولیه را می‌سازد. Recomposition چندین بار هنگام تغییر داده‌ها رخ می‌دهد: سیستم فقط توابعی را که به وضعیت تغییر یافته وابسته هستند، راه‌اندازی مجدد می‌کند.

حالت Composition تمام گره‌های درخت را فعال می‌کند، برای هر تابع slot اختصاص می‌دهد، تمام فرزندان را ثبت می‌کند. Recomposition به صورت انتخابی کار می‌کند: Compose مقادیر جدید و قدیم پارامترهای هر تابع را مقایسه می‌کند و اگر تغییر نکرده باشند — تابع اجرا نمی‌شود (skipping).

Composition و Recomposition از نظر هزینه متفاوت هستند. اولین Composition گران‌تر است، زیرا نیاز به ساخت کامل درخت و اختصاص slotها دارد. Recomposition ارزان‌تر است، به خصوص اگر اکثر توابع stable باشند — پارامترهای آنها با equals مقایسه می‌شوند و Compose فراخوانی آنها را رد می‌کند. برای حداکثر کارایی، باید تلاش کرد تا حد امکان توابع کمتری تحت تأثیر recomposition قرار گیرند.

ویژگیCompositionRecomposition
زمان وقوعیک بار، در اولین نمایشچندین بار، هنگام تغییر داده‌ها
حجمکل درختفقط توابع تغییر یافته
مقایسه پارامترهاانجام نمی‌شودبرای skipping انجام می‌شود
ایجاد slotهابله، تمام slotها ایجاد می‌شوندفقط برای گره‌های جدید

CompositionLocal: انتقال داده از طریق درخت

CompositionLocal — مکانیزم انتقال ضمنی داده‌ها از طریق درخت ترکیب. این مشکل زمانی را حل می‌کند که یک پارامتر باید از طریق ده‌ها تابع Composable تو در تو منتقل شود که مستقیماً از آن استفاده نمی‌کنند. به جای زنجیره صریح پارامترها، داده‌ها در سطح بالایی تنظیم شده و در هر تابع تو در تو از طریق CompositionLocal.current خوانده می‌شوند.

تم MaterialTheme — معروف‌ترین مثال CompositionLocal است. تمام کامپوننت‌های Compose رنگ‌ها، تایپوگرافی و اشکال را از طریق MaterialTheme.colorScheme، MaterialTheme.typography، MaterialTheme.shapes می‌خوانند، بدون اینکه آنها را از طریق پارامترها دریافت کنند. توسعه‌دهنده می‌تواند CompositionLocalهای خود را برای داده‌هایی مانند کاربر فعلی، تنظیمات بومی‌سازی یا پیکربندی صفحه ایجاد کند.

محدودیت مهم: CompositionLocal نباید برای داده‌های مکرراً تغییرکننده (موقعیت اسکرول، متن در فیلد ورودی) استفاده شود. کامپوننتی که CompositionLocal را می‌خواند، با هر تغییر مقدار دوباره راه‌اندازی می‌شود، بنابراین برای داده‌های پویا بهتر است از پارامترهای صریح یا State استفاده شود. CompositionLocal برای داده‌های پیکربندی که به ندرت یا هرگز تغییر نمی‌کنند، بهینه است.

kotlin
val LocalUser = compositionLocalOf<User?> { null }

@Composable
fun AppRoot(user: User, content: @Composable () -> Unit) {
    CompositionLocalProvider(LocalUser.provides(user)) {
        content()
    }
}

@Composable
fun UserAvatar() {
    val user = LocalUser.current  // خواندن بدون پارامتر صریح
    AsyncImage(model = user?.avatarUrl, contentDescription = "Avatar")
}

CompositionLocalProvider یک محدوده دید ایجاد می‌کند که در داخل آن LocalUser.current مقدار تعیین شده را برمی‌گرداند. UserAvatar کاربر را بدون انتقال صریح پارامتر از طریق توابع میانی می‌خواند. این امر به ویژه در سلسله‌مراتب عمیق که داده‌ها فقط در چند گره برگ مورد نیاز است، ارزشمند است.

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

اگر State در طول Composition تغییر کند چه اتفاقی می‌افتد؟

تغییر State در طول Composition یک recomposition جدید برنامه‌ریزی می‌کند که پس از اتمام جاری اجرا می‌شود. حلقه بی‌نهایت رخ نمی‌دهد: Compose تضمین می‌کند که هر recomposition در یک تراکنش جداگانه از سیستم snapshot اجرا می‌شود.

Composition یک صفحه پیچیده چقدر زمان می‌برد؟

در دستگاه‌های مدرن، Composition صفحه با 50–100 تابع Composable حدود 1–5 ms طول می‌کشد. Google توصیه می‌کند برای فریم 60fps در 16 ms بگنجید. اگر Composition از این حد فراتر رفت، از LazyColumn استفاده کنید یا صفحه را به توابع کوچک‌تر تقسیم کنید.

آیا می‌توان Composition را به صورت دستی اجرا کرد؟

اجرای دستی مستقیم Composition ممکن نیست — به طور خودکار توسط Composer مدیریت می‌شود. با این حال، می‌توان با تغییر State یا فراخوانی invalidate() در کامپوزیت ریشه، اگر به CompositionContext دسترسی وجود دارد، recomposition را اجباری برنامه‌ریزی کرد.

Composition چه تفاوتی با سلسله‌مراتب View در Android کلاسیک دارد؟

سلسله‌مراتب View — درختی تغییرناپذیر از اشیاء Java است که یک بار ایجاد می‌شود. Composition — درختی مجازی است که با هر تغییر داده بازسازی می‌شود. View وضعیت خود را در متغیرهای نمونه ذخیره می‌کند، Composition — در slotهای متصل به موقعیت فراخوانی تابع.

Composition حذف گره‌ها را چگونه مدیریت می‌کند؟

اگر تابع Composable دیگر فراخوانی نشود (مثلاً شرط if false شود)، Composition گره آن را حذف کرده و پاکسازی DisposableEffect را فراخوانی می‌کند. هنگام ظهور مجدد (if دوباره true) یک گره جدید ایجاد می‌شود — گره قدیمی بازیابی نمی‌شود.

خلاصه

  • Composition — فرآیند اجرای توابع Composable برای ساخت درخت UI متصل به وضعیت
  • Composer slotها را مدیریت می‌کند، فراخوانی توابع را ثبت کرده و پارامترها را در recomposition مقایسه می‌کند
  • سیستم snapshot وابستگی توابع به State را ثبت کرده و تغییرات را در تراکنش‌ها ترکیب می‌کند
  • Composition یک بار در شروع، Recomposition — هنگام تغییر داده‌ها اجرا می‌شود
  • CompositionLocal داده‌های پیکربندی را از طریق درخت بدون زنجیره صریح پارامترها منتقل می‌کند
  • موقعیت فراخوانی تابع به عنوان شناسه منحصر به فرد آن در درخت ترکیب عمل می‌کند
  • توصیه: توابع Composable را کوچک با پارامترهای غیرقابل تغییر برای skipping مؤثر ایجاد کنید

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

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

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

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