מחזור החיים של תכנים קומפוזביליים

בדף הזה תלמדו על מחזור החיים של תוכן קומפוזבילי האופן שבו התכונה Compose קובע אם תוכן קומפוזבילי צריך להרכיב מחדש.

סקירה כללית של מחזור החיים

כפי שצוין במסמכים לגבי ניהול מצב, הרכב מתאר את ממשק המשתמש של האפליקציה ומופק על ידי הרצה של תכנים קומפוזביליים. הרכב הוא מבנה עץ של התכנים הקומפוזביליים שמתארים את ממשק המשתמש שלכם.

כש-Jetpack Compose מפעיל את ה-composables בפעם הראשונה, במהלך הרכבת הקוד הראשונית, הוא עוקב אחרי ה-composables שאתם קוראים להם כדי לתאר את ממשק המשתמש ב-Composition. לאחר מכן, כשמצב האפליקציה ישתנה, Jetpack Compose יתזמן יצירה מחדש. הרכבה מחדש היא כש-Jetpack פיתוח נייטיב מפעיל מחדש את התכנים הקומפוזביליים שייתכן שהשתנו בתגובה לשינויים במצב, ולאחר מכן מעדכנת את היצירה כך שתשקף את השינויים, אם ישנם.

אפשר ליצור קומפוזיציה רק באמצעות קומפוזיציה ראשונית, ולעדכן אותה באמצעות קומפוזיציה מחדש. הדרך היחידה לשנות יצירה מוזיקלית היא באמצעות הרכבה מחדש.

תרשים שבו מוצג מחזור החיים של רכיב מורכב

איור 1. מחזור החיים של רכיב ה-Composable ב-Composition. הוא מזין את יצירה מוזיקלית, הרכבה מחדש 0 פעמים או יותר, ויוצאת מהיצירה.

הרכבה מחדש בדרך כלל מופעלת על ידי שינוי אובייקט State<T>. אימייל חדש שעוקבת אחריה ומריצה את כל התכנים הקומפוזביליים במערך היצירה שקוראת State<T> מסוים, וכל תוכן קומפוזבילי שהוא קורא לתוכן שלא יכול דילגת.

אם קוראים ל-composable כמה פעמים, המערכת תוסיף כמה מכונות ל-Composition. לכל קריאה יש מחזור חיים משלה ב-Composition.

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

תרשים שבו מוצגת ההיררכיה של הרכיבים בקטע הקוד הקודם

איור 2. ייצוג של MyComposable ביצירה. אם נקרא 'קומפוזביליות' כמה פעמים, יצירה מוזיקלית. שרכיב בצבע אחר מצביע על כך שהוא למופע נפרד.

האנטומיה של חומר קומפוזבילי ביצירה

המופע של תוכן קומפוזבילי בקטע 'יצירה' מזוהה על ידי אתר הקריאה שלו. המהדר של 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 יקרא ל-composable של LoginError באופן מותנה, ותמיד יקרא ל-composable של LoginInput. כל אחד הקריאה כוללת אתר התקשרות ומיקום מקור ייחודיים, שבהם המהדר ישתמש כדי לזהות אותו באופן ייחודי.

תרשים שמראה איך הקוד הקודם מורכב מחדש אם דגל showError משתנה ל-True. הרכיב ה-composable LoginError מתווסף, אבל הרכיבים האחרים לא מורכבים מחדש.

איור 3. ייצוג של LoginScreen בהרכבה כשהמצב משתנה ומתבצעת הרכבה מחדש. אם הצבע זהה, סימן שהקמפיין לא עבר עיבוד מחדש.

למרות ש-LoginInput עברה מקריאה ראשונה לקריאה שנייה, המכונה LoginInput תישמר בכל הרכבות מחדש. בנוסף, כי אין למשתמש LoginInput פרמטרים שהשתנו מחדש, תדלג על הקריאה ל-LoginInput על ידי 'כתיבה'.

הוספת מידע נוסף כדי לשפר את הרכבות החכמות מחדש

קריאה ל-composable מספר פעמים תוסיף אותו ל-Composition מספר פעמים גם כן. כשקוראים ל-composable כמה פעמים מאותו אתר קריאה, ל-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)
        }
    }
}

בדוגמה שלמעלה, ההצעות לכתיבה משתמשות בסדר הביצוע בנוסף לקריאה כדי שהמכונה תהיה ייחודית ב-Composition. אם יתווסף movie חדש לתחתית הרשימה, התכונה 'כתיבה' יכולה לעשות שימוש חוזר במכונות שכבר הרכב מאז המיקום שלו ברשימה לא השתנה, ולכן הקלט movie זהה במופעים האלה.

תרשים שמראה איך הקוד הקודם מורכב מחדש אם מתווסף רכיב חדש לתחתית הרשימה. המיקום של שאר הפריטים ברשימה לא השתנה, והם לא מורכבים מחדש.

איור 4. ייצוג של MoviesScreen בהרכבה כשמוסיפים רכיב חדש לתחתית הרשימה. MovieOverview תכנים קומפוזביליים ב ניתן לעשות שימוש חוזר ביצירה. משמעות צבע זהה ב-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 ביצירה כשמוסיפים רכיב חדש לרשימה. אי אפשר לעשות שימוש חוזר ב-composables של MovieOverview, וכל תופעות הלוואי יתחילו מחדש. צבע שונה ב-MovieOverview מציין שהרכיב המודולרי עוצב מחדש.

באופן אידיאלי, הזהות של מכונה MovieOverview מקושרת לזהות של movie שמועברת אליה. אם נזמין מחדש את של סרטים, באופן אידיאלי נסדר מחדש את המופעים באופן דומה עץ הרכב במקום להרכיב מחדש כל תוכן קומפוזבילי של MovieOverview באמצעות למופע של סרט אחר. פיתוח נייטיב מאפשר לך לציין את זמן הריצה באילו ערכים להשתמש כדי לזהות חלק מסוים בעץ: 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 ויוכל לעשות בהן שימוש חוזר.

תרשים שמראה איך הקוד הקודם מורכב מחדש אם רכיב חדש מתווסף לראש הרשימה. מאחר שהפריטים ברשימה מזוהים באמצעות מפתחות, התכונה &#39;כתיבה&#39; יודעת לא להרכיב אותם מחדש, למרות שהמיקום שלהם השתנה.

איור 6. ייצוג של MoviesScreen ביצירה כאשר יצירה חדשה נוסף לרשימה. מכיוון שלרכיבי ה-Compose של MovieOverview יש מפתחות ייחודיים, Compose מזהה אילו מכונות MovieOverview לא השתנו ויכול לעשות בהן שימוש חוזר. ההשפעות הצדדיות שלהן ימשיכו לפעול.

לחלק מהרכיבים הניתנים לשילוב יש תמיכה מובנית ברכיב key הניתן לשילוב. לדוגמה, אפשר לציין key בהתאמה אישית ב-items DSL ב-LazyColumn.

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

דילוג אם הקלט לא השתנה

במהלך הרכבה מחדש, לחלק מהפונקציות הקומפוזביליות שעומדות בדרישות יכולות להיות המערכת תדלג לגמרי על ההפעלה אם הקלט שלהם לא השתנה של משפטים יחידים,

אפשר לדלג על פונקציה קומפוזבילית אלא אם:

  • פונקציה מסוג 'החזרה' היא לא Unit
  • לפונקציה יש הערות עם @NonRestartableComposable או @NonSkippableComposable
  • פרמטר נדרש הוא מסוג לא יציב

יש מצב מהדר (compiler) ניסיוני, דילוג חזק, שמפחיתה את הדרישה האחרונה.

כדי שסוג ייחשב כיציב, הוא צריך לעמוד בהסכם הבא:

  • התוצאה של equals עבור שתי מופעים תמיד תהיה זהה ושני מקרים זהים.
  • אם נכס ציבורי מהסוג משתנה, תישלח הודעה על היצירה.
  • גם כל סוגי הנכסים הציבוריים יציבים.

בחוזה הזה יש כמה סוגים נפוצים וחשובים שלפיהם מהדר לכתובים יתייחס ליציבות, גם אם לא נעשה שימוש מפורש מסומן כיציב באמצעות ההערה @Stable:

  • כל סוגי הערכים הפרימיטיביים: Boolean,‏ Int,‏ Long,‏ Float,‏ Char וכו'.
  • מיתרים
  • כל סוגי הפונקציות (lambda)

כל הסוגים האלה יכולים לפעול בהתאם לחוזה של היציבות, שלא ניתן לשינוי. מכיוון שסוגי immutable אף פעם לא משתנים, הם אף פעם לא צריכים להודיע ל-Composition על השינוי, ולכן קל הרבה יותר לפעול בהתאם להסכם הזה.

סוג אחד ראוי לציון שהוא יציב אבל ניתן לשינוי הוא הסוג MutableState של Compose. אם ערך נשמר ב-MutableState, האובייקט הכולל של המצב הוא נחשבת כיציבה כי 'כתיבה' יקבל הודעה על כל שינוי מאפיין .value של State.

כשכל הסוגים המועברים כפרמטרים לרכיב מורכב הם יציבים, מתבצעת השוואה בין ערכי הפרמטרים כדי לבדוק אם הם זהים, על סמך המיקום של הרכיב המורכב בעץ של ממשק המשתמש. המערכת תדלג על הרכבת מחדש אם כל הערכים לא השתנו מאז הקריאה הקודמת.

סוג כתיבה נחשב כיציב רק אם ניתן להוכיח אותו. לדוגמה, בדרך כלל נתייחס לממשק כאל לא יציב, וגם לסוגים עם מאפיינים ציבוריים שניתנים לשינוי שההטמעה שלהם עשויה להיות בלתי ניתנת לשינוי.

אם התכונה 'כתיבה' לא יכולה להסיק שסוג מסוים הוא יציב, אבל אתם רוצים לאלץ אפשר לכתוב 'אני רוצה' כדי להתייחס לגרסה יציבה ולסמן אותה באמצעות @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 מציינים ל'כתיבה' שהסוג הזה יציב, והוא מאפשר ל'כתיבה' ליהנות הרכבים מחדש חכמים. פירוש הדבר הוא גם ש'כתיבה' תטפל בכל או הטמעות יציבות, אם הממשק משמש כסוג הפרמטר.