مراحل نشاط التركيبات

في هذه الصفحة، ستتعرّف على مراحل نشاط الدالة المركّبة وكيفية تحديد Compose ما إذا كانت الدالة المركّبة بحاجة إلى إعادة التكوين.

نظرة عامة على مراحل النشاط

كما هو موضّح في مستند إدارة الحالة، يصف التركيب واجهة مستخدم تطبيقك ويتم إنشاؤه من خلال تشغيل العناصر القابلة للإنشاء. التركيب هو بنية على شكل شجرة من العناصر القابلة للإنشاء التي تصف واجهة المستخدم.

عندما يشغّل Jetpack Compose العناصر القابلة للإنشاء للمرة الأولى، أثناء عملية الإنشاء الأولية، سيتتبّع العناصر القابلة للإنشاء التي تستدعيها لوصف واجهة المستخدم في عملية الإنشاء. بعد ذلك، عندما تتغير حالة تطبيقك، يجدول Jetpack Compose إعادة التكوين. تحدث إعادة التركيب عندما يعيد Jetpack Compose تنفيذ العناصر القابلة للإنشاء التي ربما تغيّرت استجابةً لتغييرات الحالة، ثم يعدّل التركيب لعرض أي تغييرات.

لا يمكن إنشاء التكوين إلا من خلال تكوين أولي، ولا يمكن تعديله إلا من خلال إعادة التكوين. الطريقة الوحيدة لتعديل "مقطوعة موسيقية" هي إعادة تأليفها.

مخطّط بياني يوضّح دورة حياة دالة مركّبة
الشكل 1. دورة حياة عنصر قابل للإنشاء في Composition يدخل إلى التركيب، ويتم إعادة تركيبه 0 مرة أو أكثر، ثم يخرج من التركيب.

يتم عادةً إعادة التركيب عند إجراء تغيير على كائن State<T>. تتتبّع Compose هذه العمليات وتنفّذ جميع الدوال المركّبة في التكوين التي تقرأ State<T> معيّنًا، وأي دوال مركّبة تستدعيها ولا يمكن تخطّيها.

إذا تم استدعاء دالة مركّبة عدة مرات، سيتم وضع عدة مثيلات في التكوين. لكل مكالمة دورة حياة خاصة بها في Composition.

@Composable
fun MyComposable() {
    Column {
        Text("Hello")
        Text("World")
    }
}

مخطّط بياني يعرض الترتيب الهرمي للعناصر في مقتطف الرمز البرمجي السابق
الشكل 2. تمثيل MyComposable في المقطوعة الموسيقية إذا تم استدعاء عنصر قابل للإنشاء عدة مرات، يتم وضع عدة مثيلات في Composition. يشير العنصر الذي له لون مختلف إلى أنّه نسخة منفصلة.

بنية دالة مركّبة في التكوين

يتم تحديد مثيل عنصر قابل للإنشاء في "التركيب" من خلال موقع الاستدعاء. يعدّ برنامج التجميع في Compose كل موقع اتصال مختلفًا عن الآخر. سيؤدي استدعاء دوال قابلة للإنشاء من مواقع استدعاء متعددة إلى إنشاء مثيلات متعددة للدالة القابلة للإنشاء في Composition.

إذا استدعت دالة قابلة للإنشاء دوالاً قابلة للإنشاء مختلفة أثناء إعادة التركيب مقارنةً بالتركيب السابق، سيتمكّن Compose من تحديد الدوال القابلة للإنشاء التي تم استدعاؤها أو لم يتم استدعاؤها، وبالنسبة إلى الدوال القابلة للإنشاء التي تم استدعاؤها في كلتا عمليتَي التركيب، سيحرص Compose على تجنُّب إعادة تركيبها إذا لم تتغيّر مدخلاتها.

يُعد الحفاظ على الهوية أمرًا بالغ الأهمية لربط الآثار الجانبية بالعناصر القابلة للإنشاء، حتى يمكن إكمالها بنجاح بدلاً من إعادة تشغيلها في كل عملية إعادة إنشاء.

انظر المثال التالي:

@Composable
fun LoginScreen(showError: Boolean) {
    if (showError) {
        LoginError()
    }
    LoginInput() // This call site affects where LoginInput is placed in Composition
}

@Composable
fun LoginInput() { /* ... */ }

@Composable
fun LoginError() { /* ... */ }

في مقتطف الرمز البرمجي أعلاه، سيستدعي LoginScreen بشكل مشروط الدالة البرمجية القابلة للإنشاء LoginError، وسيستدعي دائمًا الدالة البرمجية القابلة للإنشاء LoginInput. يحتوي كل استدعاء على موقع استدعاء فريد وموضع مصدر فريد، وسيستخدمه المترجم لتحديد الاستدعاء بشكل فريد.

مخطّط يوضّح كيفية إعادة إنشاء الرمز السابق إذا تم تغيير العلامة showError إلى &quot;صحيح&quot;. تمت إضافة العنصر LoginError القابل للإنشاء، ولكن لم تتم إعادة إنشاء العناصر الأخرى القابلة للإنشاء.
الشكل 3. تمثّل LoginScreen في التركيب عند تغيُّر الحالة وحدوث إعادة تركيب. يشير اللون نفسه إلى أنّه لم تتم إعادة تركيبه.

على الرغم من أنّ LoginInput انتقل من أن يتم استدعاؤه أولاً إلى أن يتم استدعاؤه ثانيًا، سيتم الاحتفاظ بنسخة LoginInput خلال عمليات إعادة الإنشاء. بالإضافة إلى ذلك، بما أنّ LoginInput لا تتضمّن أي مَعلمات تم تغييرها أثناء إعادة التركيب، سيتخطّى Compose طلب LoginInput.

إضافة معلومات إضافية للمساعدة في عمليات إعادة التكوين الذكية

سيؤدي استدعاء عنصر قابل للإنشاء عدة مرات إلى إضافته إلى Composition عدة مرات أيضًا. عند استدعاء دالة مركّبة عدة مرات من موقع الاستدعاء نفسه، لا تتوفّر لدى Compose أي معلومات لتحديد كل استدعاء لهذه الدالة بشكل فريد، لذا يتم استخدام ترتيب التنفيذ بالإضافة إلى موقع الاستدعاء من أجل الحفاظ على تمييز المثيلات. في بعض الأحيان، يكون هذا السلوك هو المطلوب، ولكن في بعض الحالات، قد يؤدي إلى سلوك غير مرغوب فيه.

@Composable
fun MoviesScreen(movies: List<Movie>) {
    Column {
        for (movie in movies) {
            // MovieOverview composables are placed in Composition given its
            // index position in the for loop
            MovieOverview(movie)
        }
    }
}

في المثال أعلاه، يستخدم Compose ترتيب التنفيذ بالإضافة إلى موقع الاستدعاء للحفاظ على تمييز المثيل في التركيب. إذا تمت إضافة movie جديد إلى أسفل القائمة، يمكن أن تعيد Compose استخدام العناصر التي سبق إضافتها إلى Composition لأنّ موضعها في القائمة لم يتغيّر، وبالتالي، تكون قيمة الإدخال movie هي نفسها بالنسبة إلى هذه العناصر.

مخطّط بياني يوضّح كيفية إعادة إنشاء الرمز السابق في حال إضافة عنصر جديد إلى أسفل القائمة لم تتغيّر مواضع العناصر الأخرى في القائمة، ولم تتم إعادة ترتيبها.
الشكل 4. تمثيل MoviesScreen في Composition عند إضافة عنصر جديد إلى أسفل القائمة يمكن إعادة استخدام الدوال البرمجية القابلة للإنشاء MovieOverview في Composition. يشير اللون نفسه في MovieOverview إلى أنّه لم تتم إعادة إنشاء العنصر القابل للإنشاء.

ومع ذلك، إذا تغيّرت قائمة movies من خلال إضافة عناصر إلى أعلى القائمة أو وسطها أو إزالة عناصر أو إعادة ترتيبها، سيؤدي ذلك إلى إعادة التكوين في جميع طلبات MovieOverview التي تغيّر موضع مَعلمة الإدخال فيها في القائمة. وهذا مهم للغاية إذا كان MovieOverview، على سبيل المثال، يجلب صورة فيلم باستخدام تأثير جانبي. إذا تمت إعادة التركيب أثناء تنفيذ التأثير، سيتم إلغاؤه وإعادة بدئه.

@Composable
fun MovieOverview(movie: Movie) {
    Column {
        // Side effect explained later in the docs. If MovieOverview
        // recomposes, while fetching the image is in progress,
        // it is cancelled and restarted.
        val image = loadNetworkImage(movie.url)
        MovieHeader(image)

        /* ... */
    }
}

مخطّط بياني يوضّح كيفية إعادة إنشاء الرمز السابق في حال إضافة عنصر جديد إلى أعلى القائمة يتغير موضع كل عنصر آخر في القائمة ويجب إعادة تركيبه.
الشكل 5. تمثيل MoviesScreen في Composition عند إضافة عنصر جديد إلى القائمة. لا يمكن إعادة استخدام الدوال البرمجية القابلة للإنشاء MovieOverview، وستتم إعادة تشغيل جميع الآثار الجانبية. يشير اللون المختلف في MovieOverview إلى أنّه تمت إعادة إنشاء العنصر القابل للإنشاء.

من الناحية المثالية، نريد أن نربط هوية مثيل MovieOverview بهوية movie التي يتم تمريرها إليه. إذا أعدنا ترتيب قائمة الأفلام، من المفترض أن نعيد ترتيب العناصر في شجرة Composition بشكل مماثل بدلاً من إعادة إنشاء كل عنصر MovieOverview قابل للإنشاء باستخدام عنصر فيلم مختلف. توفّر Compose طريقة لتحديد القيم التي تريد استخدامها للتعريف عن جزء معيّن من الشجرة في وقت التشغيل، أي key القابل للإنشاء.

من خلال تضمين مجموعة من الرموز البرمجية في استدعاء للدالة القابلة للإنشاء الخاصة بالمفتاح مع تمرير قيمة واحدة أو أكثر، سيتم دمج هذه القيم لاستخدامها في تحديد هذا العنصر في التركيب. لا يشترط أن تكون قيمة key فريدة على مستوى العالم، بل يجب أن تكون فريدة فقط بين استدعاءات العناصر القابلة للإنشاء في موقع الاستدعاء. في هذا المثال، يجب أن يحتوي كل movie على key فريد ضمن movies، ولا بأس إذا كان key مشتركًا مع بعض الدوال المركّبة الأخرى في مكان آخر في التطبيق.

@Composable
fun MoviesScreenWithKey(movies: List<Movie>) {
    Column {
        for (movie in movies) {
            key(movie.id) { // Unique ID for this movie
                MovieOverview(movie)
            }
        }
    }
}

مع ما سبق، حتى إذا تغيّرت العناصر في القائمة، يتعرّف Compose على عمليات الاستدعاء الفردية للدالة MovieOverview ويمكنه إعادة استخدامها.

مخطّط بياني يوضّح كيفية إعادة إنشاء الرمز السابق في حال إضافة عنصر جديد إلى أعلى القائمة بما أنّ عناصر القائمة يتم تحديدها من خلال المفاتيح، يعرف Compose أنّه لا يعيد إنشاءها، حتى إذا تغيّرت مواضعها.
الشكل 6. تمثيل MoviesScreen في Composition عند إضافة عنصر جديد إلى القائمة. بما أنّ عناصر MovieOverview المركّبة تتضمّن مفاتيح فريدة، يتعرّف Compose على مثيلات MovieOverview التي لم تتغيّر ويمكنه إعادة استخدامها، وسيستمر تنفيذ آثارها الجانبية.

تتضمّن بعض الدوال المركّبة إمكانية استخدام الدالة المركّبة key. على سبيل المثال، تقبل LazyColumn تحديد key مخصّص في items DSL.

@Composable
fun MoviesScreenLazy(movies: List<Movie>) {
    LazyColumn {
        items(movies, key = { movie -> movie.id }) { movie ->
            MovieOverview(movie)
        }
    }
}

تخطّي الخطوة إذا لم تتغيّر المعلومات

أثناء إعادة التكوين، يمكن تخطّي تنفيذ بعض الدوال البرمجية القابلة للإنشاء المؤهّلة بالكامل إذا لم تتغيّر مُدخلاتها عن عملية التكوين السابقة.

تكون الدالة المركّبة مؤهَّلة للتخطّي إلا في الحالات التالية:

  • تحتوي الدالة على نوع إرجاع غير Unit
  • يتم إضافة تعليق توضيحي إلى الدالة باستخدام @NonRestartableComposable أو @NonSkippableComposable
  • المَعلمة المطلوبة من نوع غير ثابت

يتوفّر وضع للمترجم، وهو التخطّي القوي، الذي يخفّف من الشرط الأخير.

لكي يُصنّف النوع على أنّه مستقر، يجب أن يلتزم بالعقد التالي:

  • ستكون نتيجة equals لمثيلَين دائمًا هي نفسها بالنسبة إلى المثيلَين نفسيهما.
  • إذا تغيّرت سمة عامة من النوع، سيتم إعلام Composition بذلك.
  • جميع أنواع المواقع العامة ثابتة أيضًا.

هناك بعض الأنواع الشائعة المهمة التي تندرج ضمن هذا العقد والتي سيتعامل معها برنامج التجميع في Compose على أنّها ثابتة، على الرغم من أنّه لم يتم تصنيفها بشكل صريح على أنّها ثابتة باستخدام التعليق التوضيحي @Stable:

  • جميع أنواع القيم الأساسية: Boolean وInt وLong وFloat وChar وما إلى ذلك
  • سلاسل نصية
  • جميع أنواع الدوال (lambdas)

ويمكن لجميع هذه الأنواع اتّباع عقد الثبات لأنّها غير قابلة للتغيير. بما أنّ الأنواع غير القابلة للتغيير لا تتغير أبدًا، لا يلزمها إعلام Composition بالتغيير، لذا يكون من الأسهل بكثير اتّباع هذا العقد.

أحد الأنواع البارزة الثابتة ولكن قابلة للتغيير هو النوع MutableState في Compose. إذا كانت القيمة مخزّنة في MutableState، سيتم اعتبار كائن الحالة بشكل عام ثابتًا لأنّ Compose سيتم إعلامه بأي تغييرات تطرأ على السمة .value في State.

عندما تكون جميع الأنواع التي تم تمريرها كمَعلمات إلى دالة مركّبة مستقرة، تتم مقارنة قيم المَعلمات من حيث المساواة استنادًا إلى موضع الدالة المركّبة في شجرة واجهة المستخدم. يتم تخطّي إعادة التركيب إذا لم تتغيّر أي من القيم منذ الاستدعاء السابق.

لا تعتبر Compose نوعًا ثابتًا إلا إذا كان بإمكانها إثبات ذلك. على سبيل المثال، يتم عادةً التعامل مع الواجهة على أنّها غير ثابتة، كما أنّ الأنواع التي تتضمّن سمات عامة قابلة للتغيير يمكن أن يكون تنفيذها غير قابل للتغيير، وبالتالي تكون غير ثابتة أيضًا.

إذا لم يتمكّن Compose من استنتاج أنّ النوع ثابت، ولكنك تريد فرض معاملته على أنّه ثابت، يمكنك إضافة التعليق التوضيحي @Stable إليه.

// Marking the type as stable to favor skipping and smart recompositions.
@Stable
interface UiState<T : Result<T>> {
    val value: T?
    val exception: Throwable?

    val hasError: Boolean
        get() = exception != null
}

في مقتطف الرمز البرمجي أعلاه، بما أنّ UiState هي واجهة، يمكن أن يعتبرها Compose عادةً نوعًا غير ثابت. من خلال إضافة التعليق التوضيحي @Stable ، تخبر Compose أنّ هذا النوع ثابت، ما يسمح لـ Compose بتفضيل عمليات إعادة التركيب الذكية. يعني هذا أيضًا أنّ Compose ستتعامل مع جميع عمليات التنفيذ على أنّها ثابتة إذا تم استخدام الواجهة كنوع المَعلمة.