چرخه حیات مواد ترکیب پذیر

در این صفحه، در مورد چرخه حیات یک composable و اینکه Compose چگونه تصمیم می‌گیرد که یک composable نیاز به ترکیب مجدد دارد، خواهید آموخت.

مروری بر چرخه حیات

همانطور که در مستندات مدیریت وضعیت ذکر شد، یک Composition رابط کاربری برنامه شما را توصیف می کند و با اجرای composable ها تولید می شود. ترکیب یک ساختار درختی از اجزای سازنده است که رابط کاربری شما را توصیف می کند.

هنگامی که Jetpack Compose برای اولین بار فایل‌های composable شما را اجرا می‌کند، در طول ترکیب اولیه ، مواردی را که برای توصیف رابط کاربری خود در یک Composition فرا می‌خوانید، پیگیری می‌کند. سپس، هنگامی که وضعیت برنامه شما تغییر می کند، Jetpack Compose یک ترکیب مجدد را برنامه ریزی می کند. Recomposition زمانی است که Jetpack Compose اجزای تشکیل دهنده را که ممکن است در پاسخ به تغییرات حالت تغییر کرده باشند را مجدداً اجرا می کند و سپس ترکیب را به روز می کند تا هرگونه تغییر را منعکس کند.

یک ترکیب تنها می تواند با یک ترکیب اولیه تولید شود و با ترکیب مجدد به روز شود. تنها راه برای اصلاح یک ترکیب، از طریق ترکیب مجدد است.

نموداری که چرخه زندگی یک کامپوزیشن را نشان می دهد

شکل 1. چرخه حیات یک ترکیب پذیر در ترکیب. وارد Composition می شود، 0 بار یا بیشتر دوباره ترکیب می شود و از Composition خارج می شود.

ترکیب مجدد معمولاً با تغییر به یک شیء State<T> آغاز می شود. Compose اینها را دنبال می‌کند و همه کامپوزیتی‌ها را در Composition اجرا می‌کند که آن State<T> می‌خوانند، و هر composableهایی را که آنها فراخوانی می‌کنند و نمی‌توان از آن گذشت .

اگر یک composable چندین بار فراخوانی شود، چندین نمونه در Composition قرار می گیرد. هر فراخوانی چرخه حیات خاص خود را در Composition دارد.

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

نموداری که آرایش سلسله مراتبی عناصر در قطعه کد قبلی را نشان می دهد

شکل 2. نمایش MyComposable در ترکیب. اگر یک composable چندین بار فراخوانی شود، چندین نمونه در Composition قرار می گیرد. عنصری که رنگ متفاوتی دارد نشان دهنده آن است که یک نمونه جداگانه است.

آناتومی یک ترکیب پذیر در ترکیب

نمونه یک composable در Composition توسط سایت فراخوانی آن مشخص می شود. کامپایلر Compose هر سایت تماس را مجزا در نظر می گیرد. فراخوانی composable ها از چندین سایت تماس، چندین نمونه از composable را در Composition ایجاد می کند.

اگر در طول یک ترکیب مجدد، یک Composable ترکیب‌های متفاوتی را نسبت به ترکیب قبلی فراخوانی کرد، 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 composable را فراخوانی می کند و همیشه LoginInput composable را فراخوانی می کند. هر تماس دارای یک سایت تماس و موقعیت منبع منحصر به فرد است که کامپایلر از آن برای شناسایی منحصر به فرد آن استفاده می کند.

نمودار نشان می دهد که چگونه کد قبلی در صورت تغییر پرچم showError به true دوباره ترکیب می شود. LoginError composable اضافه شده است، اما دیگر composable ها دوباره ساخته نمی شوند.

شکل 3. نمایش LoginScreen در ترکیب زمانی که حالت تغییر می کند و یک ترکیب مجدد رخ می دهد. همان رنگ به این معنی است که دوباره ترکیب نشده است.

حتی اگر LoginInput از اولین فراخوانی به فراخوانی دوم تبدیل شد، نمونه LoginInput در بین ترکیبات مجدد حفظ خواهد شد. علاوه بر این، از آنجا که LoginInput هیچ پارامتری ندارد که در ترکیب مجدد تغییر کرده باشد، فراخوانی LoginInput توسط Compose نادیده گرفته می‌شود.

اطلاعات اضافی را برای کمک به ترکیب مجدد هوشمند اضافه کنید

چندین بار فراخوانی یک Composable آن را نیز چندین بار به Composition اضافه می کند. هنگام فراخوانی چندین بار از یک سایت تماس یکسان، Compose هیچ اطلاعاتی برای شناسایی منحصر به فرد هر تماس با آن composable ندارد، بنابراین برای متمایز نگه داشتن نمونه‌ها از دستور اجرا علاوه بر سایت تماس استفاده می‌شود. این رفتار گاهی تنها چیزی است که لازم است، اما در برخی موارد می تواند باعث رفتار ناخواسته شود.

@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 علاوه بر سایت فراخوانی، از دستور اجرا استفاده می کند تا نمونه را در Composition متمایز نگه دارد. اگر یک movie جدید به انتهای لیست اضافه شود، Compose می‌تواند از نمونه‌هایی که قبلاً در ترکیب قرار دارند، دوباره استفاده کند، زیرا مکان آنها در لیست تغییر نکرده است و بنابراین، ورودی movie برای آن نمونه‌ها یکسان است.

نموداری که نشان می دهد اگر یک عنصر جدید به انتهای لیست اضافه شود، چگونه کد قبلی دوباره ترکیب می شود. سایر موارد موجود در لیست موقعیت خود را تغییر نداده اند و دوباره ترکیب نشده اند.

شکل 4. نمایش MoviesScreen در ترکیب زمانی که یک عنصر جدید به انتهای لیست اضافه می شود. ترکیب‌پذیرهای 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 در ترکیب زمانی که یک عنصر جدید به لیست اضافه می شود. ترکیب‌کننده‌های MovieOverview را نمی‌توان مجدداً استفاده کرد و همه عوارض جانبی دوباره شروع می‌شوند. رنگ متفاوت در MovieOverview به این معنی است که قابلیت ترکیب دوباره ترکیب شده است.

در حالت ایده‌آل، ما می‌خواهیم هویت نمونه MovieOverview را به هویت movie که به آن منتقل می‌شود مرتبط بدانیم. اگر فهرست فیلم‌ها را دوباره ترتیب دهیم، در حالت ایده‌آل، به‌طور مشابه به جای اینکه هر MovieOverview را با یک نمونه فیلم متفاوت ترکیب کنیم، نمونه‌های موجود در درخت Composition را دوباره ترتیب می‌دهیم. 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 در ترکیب زمانی که یک عنصر جدید به لیست اضافه می شود. از آنجایی که اجزای سازنده MovieOverview دارای کلیدهای منحصربه‌فرد هستند، Compose تشخیص می‌دهد که کدام نمونه‌های MovieOverview تغییر نکرده‌اند و می‌تواند دوباره از آنها استفاده کند. عوارض جانبی آنها ادامه خواهد داشت.

برخی از composable ها دارای پشتیبانی داخلی برای composable key هستند. به عنوان مثال، LazyColumn تعیین یک key سفارشی در items DSL را می پذیرد.

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

اگر ورودی‌ها تغییر نکرده باشند، رد می‌شوند

در طول ترکیب مجدد، در صورتی که ورودی های آنها نسبت به ترکیب قبلی تغییر نکرده باشد، می توان اجرای برخی از توابع قابل ترکیب واجد شرایط را به طور کامل نادیده گرفت.

یک تابع قابل ترکیب برای پرش واجد شرایط است مگر اینکه :

  • تابع دارای نوع بازگشتی غیر Unit است
  • تابع با @NonRestartableComposable یا @NonSkippableComposable حاشیه نویسی شده است
  • یک پارامتر مورد نیاز از نوع ناپایدار است

یک حالت کامپایلر آزمایشی، Strong Skipping وجود دارد که آخرین نیاز را برطرف می‌کند.

برای اینکه یک نوع پایدار در نظر گرفته شود باید با قرارداد زیر مطابقت داشته باشد:

  • نتیجه equals برای دو نمونه برای همیشه برای همان دو نمونه یکسان خواهد بود.
  • اگر یک ویژگی عمومی از نوع تغییر کند، Composition اطلاع رسانی خواهد شد.
  • همه انواع اموال عمومی نیز پایدار هستند.

برخی از انواع رایج مهم که در این قرارداد قرار می‌گیرند وجود دارند که کامپایلر Compose آن‌ها را به‌عنوان پایدار در نظر می‌گیرد، حتی اگر با استفاده از حاشیه‌نویسی @Stable به‌صراحت به‌عنوان پایدار علامت‌گذاری نشده‌اند:

  • همه انواع مقادیر اولیه: Boolean ، Int ، Long ، Float ، Char و غیره.
  • رشته ها
  • همه انواع تابع (لامبدا)

همه این اقسام به دلیل غیرقابل تغییر بودن قادر به پیروی از عقد ثابت هستند. از آنجایی که انواع تغییرناپذیر هرگز تغییر نمی کنند، آنها هرگز نباید Composition را از تغییر مطلع کنند، بنابراین پیروی از این قرارداد بسیار ساده تر است.

یک نوع قابل توجه که پایدار است اما قابل تغییر است ، نوع MutableState Compose است. اگر مقداری در یک MutableState نگهداری شود، شی state به طور کلی پایدار در نظر گرفته می شود زیرا Compose از هرگونه تغییر در ویژگی .value State مطلع می شود.

وقتی همه انواع ارسال شده به عنوان پارامتر به یک composable پایدار هستند، مقادیر پارامتر برای برابری بر اساس موقعیت قابل ترکیب در درخت UI مقایسه می‌شوند. اگر همه مقادیر نسبت به تماس قبلی تغییری نکرده باشند، از ترکیب مجدد صرفنظر می شود.

Compose فقط در صورتی یک نوع را ثابت می داند که بتواند آن را ثابت کند. به عنوان مثال، یک رابط به طور کلی به عنوان ناپایدار تلقی می شود، و انواع با ویژگی های عمومی قابل تغییر که اجرای آنها می تواند تغییرناپذیر باشد نیز پایدار نیستند.

اگر 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 تمام پیاده‌سازی‌های خود را پایدار می‌کند.

{% کلمه به کلمه %} {% آخر کلمه %} {% کلمه به کلمه %} {% آخر کلمه %}