Recomposition هي آلية في Jetpack Compose تعيد بناء أجزاء من واجهة المستخدم تلقائياً عند تغيير البيانات، دون تحديث يدوي لعناصر View. عندما يتغير متغير حالة تعتمد عليه دالة Composable، يعيد Compose تشغيل تلك الدالة فقط، تاركاً بقية شجرة UI دون تغيير. وفقاً لـ Google Android Developers, 2026، فإن الفهم الصحيح لـ Recomposition يقلل عمليات إعادة الرسم غير الضرورية بنسبة 40–60%.
النقاط الرئيسية
Recomposition هي إعادة تنفيذ دوال Composable التي شاركت بالفعل في Composition، بقيم جديدة للمعاملات أو الحالة. الهدف الرئيسي من إعادة التركيب هو مزامنة شجرة UI مع البيانات الحالية دون إعادة بناء الواجهة بالكامل من الصفر. على عكس Composition، الذي يحدث مرة واحدة، يمكن تشغيل Recomposition مئات المرات خلال عمر الشاشة.
تعمل Recomposition على مبدأ الإبطال الذكي: يتتبع Compose كائنات State التي تقرأها كل دالة Composable ويضع علامة لإعادة التشغيل فقط على تلك التي تغيرت تبعياتها. يتم تحقيق ذلك من خلال نظام snapshot الذي يسجل جميع عمليات قراءة State أثناء التنفيذ، و Composer الذي يربط هذه التبعيات بدوال محددة.
من المهم أن نفهم: إعادة التركيب لا تعني إعادة الرسم الفوري للشاشة. يعمل Compose في ثلاث مراحل: Composition (بناء وصف UI)، Layout (حساب الأحجام والمواضع)، و Drawing (العرض على اللوحة). إذا لم تتغير أحجام ومواضع العناصر بعد إعادة التركيب، يمكن تخطي مرحلة Layout. إذا لم يتغير المظهر البصري — يتم تخطي Drawing. تضمن هذه البنية ثلاثية المراحل أقل تكلفة لكل تحديث UI.
هناك ثلاثة مشغلات رئيسية لـ إعادة التركيب. الأول هو تغيير في كائن State تمت قراءته داخل جسم دالة Composable. عندما يغير mutableStateOf أو derivedStateOf قيمته، يتم وضع علامة على جميع الدوال التي سجلت قراءة هذا State في التركيب السابق لإعادة التشغيل.
المشغل الثاني هو تغيير المعامل لدالة Composable عند استدعائها من دالة أصل. إذا مررت الدالة الأصل قيمة جديدة (على سبيل المثال، تغير النص أو الرقم)، سيتم إعادة تشغيل الدالة الفرعية، حتى لو لم تقرأ State داخلياً. يقارن Compose قيم المعاملات الجديدة والقديمة عبر equals، وإذا كانت متساوية — يمكن تخطي الدالة.
المشغل الثالث هو تغيير CompositionLocal عبر CompositionLocalProvider. جميع الدوال التي تقرأ CompositionLocal من خلال .current يتم إعادة تشغيلها عندما يتغير المزود. تستخدم MaterialTheme هذه الآلية: تغيير السمة (فاتح/داكن) يؤدي إلى إعادة تركيب جميع المكونات التي تقرأ MaterialTheme.colorScheme.
@Composable
fun RecompositionDemo() {
var counter by remember { mutableStateOf(0) }
var text by remember { mutableStateOf("Hello") }
Column {
Text("Counter: $counter") // recomposition when counter changes
Text("Message: $text") // recomposition when text changes
Button(onClick = { counter++ }) {
Text("+1")
}
Button(onClick = { text = "World" }) {
Text("Change Text")
}
}
}
النقر على الزر +1 يغير counter، مما يؤدي إلى إعادة تركيب سطر Text الأول و Column فقط. سطر Text الثاني الذي يعرض text لا يعاد تشغيله. هذا العزل هو نتيجة نظام snapshot: كل دالة Composable تعرف فقط كائنات State التي قرأتها.
يبدأ تحسين إعادة التركيب باختيار هياكل البيانات المناسبة. استخدم المجموعات غير القابلة للتغيير (listOf، mapOf) بدلاً من المجموعات القابلة للتغيير (mutableListOf). يقارن Compose المعاملات عبر equals، وإذا تغيرت مجموعة ولكن equals أرجع true — لن تعاد تشغيل الدالة. للمجموعات القابلة للتغيير، استخدم SnapshotStateList الذي يطبق تتبعاً صحيحاً للتغييرات على مستوى العناصر.
التقنية الثانية هي استخراج الأجزاء المستقرة من UI إلى دوال Composable منفصلة. إذا كان جزء من الشاشة لا يعتمد على حالة متغيرة بشكل متكرر، استخرجه إلى دالة منفصلة مع معاملات. عندما تحدث إعادة التركيب، تتلقى الدالة المستقرة نفس المعاملات، يقارنها Compose ويتخطى التنفيذ. هذا أكثر كفاءة من إعادة تشغيل ذلك الجزء كجزء من دالة كبيرة حيث تغيرت بعض المعاملات.
التقنية الثالثة هي المفاتيح في LazyColumn. حدد دائماً مفتاحاً للعناصر في LazyColumn و LazyGrid والحاويات الكسولة الأخرى. يسمح المفتاح لـ Compose بتحديد العناصر عندما تتغير القائمة: إضافة أو إزالة أو إعادة ترتيب. بدون مفتاح، يعيد Compose تشغيل جميع عناصر القائمة عند أي تغيير، مما يسبب تدهوراً ملحوظاً في الأداء في القوائم الكبيرة.
// Optimized structure: stable parts extracted separately
@Composable
fun OptimizedScreen(items: List<Item>) {
Column {
Header() // does not depend on items — no recomposition
Spacer(modifier = Modifier.height(8.dp))
LazyColumn {
items(items, key = { it.id }) { item ->
ItemRow(item = item) // recomposition only for changed items
}
}
}
}
@Composable
fun Header() {
Text("Item list", style = MaterialTheme.typography.headlineMedium)
}
@Composable
fun ItemRow(item: Item) {
Text(item.title)
}
Skipping هي آلية يتخطى فيها Compose تنفيذ دالة Composable إذا لم تتغير جميع معاملاتها. لكي يعمل التخطي بشكل صحيح، يجب أن تكون أنواع المعاملات مستقرة (stable). يضع مترجم Kotlin علامة على الأنواع التالية كمستقرة: الأنواع البدائية (Int، Float، Boolean)، String، دوال lambda، والفئات التي تكون جميع حقولها مستقرة ومؤشرة بـ val.
Stability هي التعليق @Stable أو @Immutable الذي يمكن إضافته إلى فئات البيانات المخصصة. إذا كانت الفئة تحتوي على حقل قابل للتغيير (var)، يعتبرها المترجم غير مستقرة، ولن يتمكن Compose من تخطي الدوال ذات هذه المعاملات. للفئات ذات var، استخدم @Stable إذا كنت تضمن أن إشعار التغيير سيتم إرساله من خلال نظام snapshot.
يمكنك التحقق من الاستقرار باستخدام علامة المترجم -P "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=. /reports". ينشئ تقريراً بقائمة جميع دوال Composable ومعاملاتها مع الإشارة إلى الاستقرار. إذا كان المعامل غير مستقر، فإن التخطي مستحيل لتلك الدالة وستعاد تشغيلها في كل إعادة تركيب للأصل.
| النوع | الاستقرار | التخطي |
|---|---|---|
| Int, Float, Boolean | مستقر | نعم |
| String | مستقر | نعم |
| Lambda | مستقر | نعم |
| data class مع حقول val | مستقر | نعم |
| data class مع حقول var | غير مستقر | لا |
| List<String> | غير مستقر | لا |
ملاحظة: List<String> تعتبر غير مستقرة لأنها واجهة وليس تنفيذاً ملموساً. استخدم immutableListOf() من مكتبة Kotlin Collections Immutable أو لف القائمة في فئة @Stable. Lambda دائماً مستقرة لأن equals الخاص بها يقارن المراجع فقط، وعند إنشاء lambda جديدة في موقع الاستدعاء، تعاد تشغيل الدالة الأصل أيضاً.
لمراقبة إعادة التركيب، يوفر Android Studio Layout Inspector مع وضع Compose Recomposition Counts. في هذا الوضع، تعرض كل دالة Composable عدد مرات إعادة التركيب وأسباب إعادة التشغيل. يتيح لك هذا العثور بسرعة على الدوال التي تعاد تركيبها كثيراً وتحديد السبب الجذري — معاملات غير مستقرة أو تبعيات State غير ضرورية.
أدوات إضافية: Compose Metrics (جمع الإحصائيات عبر اختبارات instrumentation) و Recomposition Timer (قياس وقت تنفيذ كل دالة). توصي Google بتمكين هذه الأدوات أثناء إنشاء ملفات التعريف وتعطيلها في إصدارات الإصدار، لأنها تضيف حملاً زائداً يصل إلى 20% لكل إعادة تركيب.
عند تحليل إعادة التركيب، ابحث عن أنماط إعادة التركيب غير الضرورية: دالة تعاد تشغيلها على الرغم من أن UI الناتج لا يجب أن يتغير. سبب شائع هو استخدام lambdas بدون remember، حيث يتم إنشاء كائن lambda جديد في كل مرة ويعتبر Compase أن المعامل قد تغير. الحل: لف lambdas في remember { } مع التقاطات ثابتة.
// Bad: new lambda on every parent recomposition
@Composable
fun Parent() {
Child(onClick = { doSomething() }) // new lambda every time
}
// Good: remember stabilizes the lambda
@Composable
fun Parent() {
val onClick = remember { { doSomething() } }
Child(onClick = onClick) // same reference
}
الأسئلة الشائعة
لا، إعادة التركيب هي فقط مرحلة Composition. بعدها يتم تنفيذ Layout و Drawing. إذا لم تتغير أحجام ومواضع العناصر بعد إعادة التركيب، يمكن تخطي Layout و Drawing بالكامل، مما يوفر موارد وحدة معالجة الرسومات.
أثناء الرسوم المتحركة، يمكن أن تعمل إعادة التركيب حتى 120 مرة في الثانية (120fps). للتفاعل العادي — 10–60 مرة في الثانية. من المهم أن تتناسب كل إعادة تركيب مع ميزانية الإطار (8–16 مللي ثانية)، وإلا سيتأخر التطبيق.
السبب هو تغيير معامل من الدالة الأصل. يعاد تشغيل الأصل (لسببه الخاص) ويمرر قيمة جديدة. لتجنب ذلك، تحقق من استقرار المعاملات واستخدم remember لتثبيت lambdas والقيم المحسوبة.
لا يوجد تعطيل مباشر، ولكن هناك تخطي قسري عبر readInComposition — يتم قراءة State خارج جسم الدالة، مما لا يسجل تبعية. استخدم هذا بحذر: لن تتفاعل الدالة مع التغييرات، مما قد يؤدي إلى UI قديم.
Composition أكثر تكلفة لأنها تنشئ جميع الفتحات وعقد الشجرة من الصفر. Recomposition تعيد استخدام الفتحات الموجودة وتحدث قيمها فقط. عملياً، Composition لشاشة تستغرق 2–10 مللي ثانية، بينما إعادة تركيب عنصر واحد تستغرق 0.1–1 مللي ثانية.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا