בדף הזה נסביר על מחזור החיים של פונקציה שאפשר להרכיב ואיך Compose מחליט אם צריך להרכיב מחדש פונקציה שאפשר להרכיב.
סקירה כללית על מחזור החיים
כמו שצוין במסמכי הניהול של מצב, קומפוזיציה מתארת את ממשק המשתמש של האפליקציה ונוצרת על ידי הפעלת פונקציות Composable. קומפוזיציה היא מבנה דמוי עץ של פונקציות Composable שמתארות את ממשק המשתמש.
כש-Jetpack Compose מפעיל את הפונקציות הניתנות להרכבה בפעם הראשונה, במהלך הרכבה ראשונית, הוא עוקב אחרי הפונקציות הניתנות להרכבה שאתם קוראים להן כדי לתאר את ממשק המשתמש בהרכבה. לאחר מכן, כשמצב האפליקציה משתנה, Jetpack Compose מתזמן קומפוזיציה מחדש. הפעלה מחדש של קומפוזיציה מתרחשת כש-Jetpack Compose מפעיל מחדש את הפונקציות הניתנות להרכבה שאולי השתנו בתגובה לשינויים במצב, ואז מעדכן את הקומפוזיציה כדי לשקף את השינויים.
אפשר ליצור קומפוזיציה רק מקומפוזיציה ראשונית, ולעדכן אותה באמצעות קומפוזיציה מחדש. הדרך היחידה לשנות קומפוזיציה היא ליצור קומפוזיציה חדשה.
איור 1. מחזור החיים של רכיב שאפשר להוסיף ליצירה. הוא נכנס לקומפוזיציה, עובר קומפוזיציה מחדש 0 פעמים או יותר, ויוצא מהקומפוזיציה.
בדרך כלל, הרכבה מחדש מופעלת בעקבות שינוי באובייקט State<T>
. Compose עוקב אחרי הערכים האלה ומריץ את כל הפונקציות הניתנות להרכבה ב-Composition שקוראות את State<T>
הספציפי הזה, וכל פונקציה ניתנת להרכבה שהן קוראות לה ושלא ניתן לדלג עליה.
אם קוראים לפונקציה composable כמה פעמים, כמה מופעים מוצבים ב-Composition. לכל שיחה יש מחזור חיים משלה ב-Composition.
@Composable fun MyComposable() { Column { Text("Hello") Text("World") } }
איור 2. ייצוג של MyComposable
בקומפוזיציה. אם קוראים לפונקציית Composable כמה פעמים, כמה מופעים מוצבים ב-Composition. אם רכיב מסוים מוצג בצבע שונה, זה מעיד על כך שהוא מופע נפרד.
המבנה של פונקציה שאפשר להרכיב ב-Compose
המופע של רכיב שאפשר להרכיב ב-Composition מזוהה לפי אתר הקריאה שלו. הקומפיילר של Compose מתייחס לכל אתר קריאה כאל אתר נפרד. קריאה לרכיבי Composable ממספר מיקומי קריאה תיצור מספר מופעים של רכיב ה-Composable ב-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
. לכל קריאה יש מיקום ייחודי באתר הקריאה ובמקור, והקומפיילר ישתמש במיקום הזה כדי לזהות אותה באופן ייחודי.
איור 3. ייצוג של LoginScreen
בהרכבה כשמצב משתנה ומתבצעת הרכבה מחדש. אם הצבע זהה, המשמעות היא שהתמונה לא הורכבה מחדש.
למרות ש-LoginInput
עבר מלהיות הראשון שנקרא להיות השני שנקרא, המופע של LoginInput
יישמר בכל ההרכבות מחדש. בנוסף,
מכיוון של-LoginInput
אין פרמטרים שהשתנו במהלך
ההרכבה מחדש, הקריאה ל-LoginInput
תדלג על Compose.
הוספת מידע נוסף כדי לשפר את ההצעות החכמות להרכבה מחדש
אם קוראים לפונקציה composable מספר פעמים, היא תתווסף לקומפוזיציה מספר פעמים. כשקוראים לפונקציה שאפשר להרכיב ממנה כמה פעמים מאותו מקום שבו מתבצעת הקריאה, לפונקציה 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 משתמש בסדר הביצוע בנוסף לאתר ההפעלה כדי לשמור על ההבדל בין המופעים ברכיב 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
בקומפוזיציה כשאלמנט חדש נוסף לרשימה. אי אפשר לעשות שימוש חוזר ב-composables של MovieOverview
וכל תופעות הלוואי יופעלו מחדש. צבע שונה ב-MovieOverview
מציין שהרכיב ניתן להרכבה מחדש.
הכי טוב לחשוב על הזהות של מופע MovieOverview
כזהות שמקושרת לזהות של movie
שמועברת אליו. אם נסדר מחדש את רשימת הסרטים, רצוי שנסדר מחדש באופן דומה את המופעים בעץ ה-Composition במקום להרכיב מחדש כל רכיב MovieOverview
עם מופע שונה של סרט. Compose מאפשרת לכם לציין בזמן הריצה אילו ערכים ישמשו לזיהוי חלק מסוים בעץ: ה-composable key
.
אם עוטפים בלוק קוד בקריאה לפונקציה הניתנת להרכבה של המפתח עם ערך אחד או יותר שמועברים, הערכים האלה ישולבו כדי לזהות את המופע הזה בקומפוזיציה. הערך של key
לא צריך להיות ייחודי באופן גלובלי, אלא רק ייחודי בין הקריאות של פונקציות Composable באתר הקריאה. לכן, בדוגמה הזו, לכל 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) } } } }
לכן, גם אם הרכיבים ברשימה משתנים, כלי הכתיבה מזהה קריאות נפרדות ל-MovieOverview
ויכול לעשות בהן שימוש חוזר.
איור 6. ייצוג של MoviesScreen
בקומפוזיציה כשאלמנט חדש נוסף לרשימה. מכיוון שלרכיבים הניתנים להרכבה MovieOverview
יש מפתחות ייחודיים, Compose מזהה אילו מופעים של MovieOverview
לא השתנו ויכול לעשות בהם שימוש חוזר. תופעות הלוואי שלהם ימשיכו לפעול.
לחלק מהקומפוזיציות יש תמיכה מובנית בקומפוזיציה key
. לדוגמה, LazyColumn
מאפשר לציין key
מותאם אישית ב-DSL של items
.
@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
וכו'. - מיתרים
- כל סוגי הפונקציות (פונקציות למבדה)
כל הסוגים האלה יכולים לפעול לפי החוזה של stable כי הם בלתי ניתנים לשינוי. מכיוון שסוגים בלתי משתנים אף פעם לא משתנים, הם אף פעם לא צריכים להודיע על השינוי, ולכן קל יותר לעקוב אחרי החוזה הזה.
סוג אחד בולט שהוא יציב אבל ניתן לשינוי הוא הסוג MutableState
של Compose. אם ערך מסוים נשמר ב-MutableState
, אובייקט המצב נחשב יציב כי Compose מקבל הודעה על כל שינוי במאפיין .value
של State
.
כשכל הסוגים מועברים כפרמטרים לרכיב שאפשר להרכיב, המערכת משווה את ערכי הפרמטרים כדי לבדוק אם הם שווים, על סמך המיקום של הרכיב בעץ ממשק המשתמש. ההרכבה מחדש תדלג אם כל הערכים לא השתנו מאז הקריאה הקודמת.
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 יתייחס לכל ההטמעות שלו כאל יציבות אם הממשק ישמש כסוג הפרמטר.
מומלץ עבורך
- הערה: טקסט הקישור מוצג כש-JavaScript מושבת
- State ו-Jetpack פיתוח נייטיב
- תופעות לוואי ב-Compose
- שמירת מצב ממשק המשתמש בכתיבה