בדף הזה נסביר על מחזור החיים של רכיב קומפוזבילי ואיך "פיתוח נייטיב" מחליט אם צריך להרכיב מחדש את הרכיב.
סקירה כללית על מחזור החיים
כמו שצוין במאמרי העזרה לניהול מצבים, קומפוזיציה מתארת את ממשק המשתמש של האפליקציה ונוצרת על ידי הפעלת רכיבים קומפוזביליים. קומפוזיציה היא מבנה דמוי עץ עם הרכיבים הקומפוזביליים שמתארות את ממשק המשתמש.
בפעם הראשונה ש-"Jetpack פיתוח נייטיב" מריץ את הרכיבים הקומפוזביליים (במהלך הקומפוזיציה הראשונית), הוא עוקב אחרי הרכיבים שאתם מפעילים כדי לתאר את ממשק המשתמש בקומפוזיציה. לאחר מכן, כשמצב האפליקציה משתנה, Jetpack פיתוח נייטיב מתזמן קומפוזיציה מחדש. קומפוזיציה מחדש מתרחשת כש-Jetpack פיתוח נייטיב מפעיל מחדש רכיבים קומפוזביליים שאולי השתנו בתגובה לשינויים במצב, ואז מעדכן את הקומפוזיציה כדי לשקף את השינויים.
אפשר ליצור קומפוזיציה רק מקומפוזיציה ראשונית, ולעדכן אותה באמצעות קומפוזיציה מחדש. הדרך היחידה לשנות קומפוזיציה היא ליצור קומפוזיציה חדשה.
בדרך כלל, קומפוזיציה מחדש מופעלת בעקבות שינוי באובייקט State<T>. "פיתוח נייטיב" עוקב אחרי השינויים האלה ומריץ את כל הרכיבים הקומפוזביליים שקוראות את State<T> הספציפי הזה, וגם כל רכיב קומפוזבילי שהם מפעילים ושלא ניתן לדלג עליו.
אם שולחים לרכיב קומפוזבילי כמה קריאות, ימוקמו מספר מופעים בקומפוזיציה. לכל קריאה ששולחים יש מחזור חיים משלה בקומפוזיציה.
@Composable fun MyComposable() { Column { Text("Hello") Text("World") } }
MyComposable בקומפוזיציה. אם שולחים לרכיב קומפוזבילי כמה קריאות, ימוקמו מספר מופעים בקומפוזיציה. אם רכיב מסוים מוצג בצבע שונה, זה מעיד על כך שהוא מופע נפרד.המבנה של רכיב קומפוזבילי
המופע של רכיב קומפוזבילי מזוהה בקומפוזיציה לפי אתר הקריאה שלו. הקומפיילר של "פיתוח נייטיב" מתייחס לכל אתר קריאה כאל אתר נפרד. שליחת קריאות לרכיבים קומפוזביליים ממספר אתרים תיצור מספר מופעים של הרכיב בקומפוזיציה.
אם במהלך קומפוזיציה מחדש, רכעב קומפוזבילי קורא לרכיבים שונים מאלה שהוא קרא להם במהלך הקומפוזיציה הקודמת, "פיתוח נייטיב" יזהה אילו רכיבים נקראו או לא נקראו. לגבי הרכיבים שנקראו בשתי הקומפוזיציות, "פיתוח נייטיב" יימנע מקומפוזיציה מחדש שלהם אם נתוני הקלט שלהם לא השתנו.
שמירה על הזהות היא חיונית כדי לשייך תופעות לוואי לרכיב הקומפוזבילי, כך שהן יוכלו להסתיים בהצלחה במקום להתחיל מחדש בכל קומפוזיציה מחדש.
דוגמה:
@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. לכל קריאה יש מיקום ייחודי באתר הקריאה ובמקור, והקומפיילר ישתמש במיקום הזה כדי לזהות אותה באופן ייחודי.
LoginScreen בקומפוזיציה כשמצב משתנה ומתבצעת קומפוזיציה מחדש. אותו צבע מציין שהתמונה לא עברה שיפור.למרות ש-LoginInput הפך מהרכיב הראשון שמופעל לשני שמופעל, המופע של LoginInput יישמר בכל הקומפוזיציות מחדש. בנוסף,
מכיוון של-LoginInput אין פרמטרים שהשתנו במהלך
הקומפוזיציה מחדש, "פיתוח נייטיב" ידלג על הקריאה ל-LoginInput.
הוספת מידע נוסף כדי לשפר את ההצעות החכמות לקומפוזיציה מחדש
אם קוראים לרכיב קומפוזבילי מספר פעמים, הוא יתווסף לקומפוזיציה מספר פעמים. כשקוראים לרכיב קומפוזבילי כמה פעמים מאותו אתר קריאה, אין לפיתוח נייטיב מידע שיאפשר לזהות באופן ייחודי כל קריאה לרכיב. לכן, בנוסף לאתר הקריאה, סדר הביצוע משמש כדי לשמור על ההבדלים בין המופעים. לפעמים ההתנהגות הזו היא כל מה שצריך, אבל במקרים מסוימים היא עלולה לגרום לפונקציונליות לא רצויה.
@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) } } }
בדוגמה שלמעלה, פיתוח נייטיב משתמש בסדר הביצוע בנוסף לאתר הקריאה כדי לשמור על ההבדל בין המופעים בקומפוזיציה. אם מוסיפים movie חדש לתחתית הרשימה, "פיתוח נייטיב" יוכל להשתמש שוב במופעים שכבר נכללים בקומפוזיציה, כי המיקום שלהם ברשימה לא השתנה, ולכן הקלט של movie זהה למופעים האלה.
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) /* ... */ } }
MoviesScreen בקומפוזיציה כשאלמנט חדש נוסף לרשימה. אי אפשר לעשות שימוש חוזר ברכיב הקומפוזבילי של 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) } } } }
לכן, גם אם הרכיבים ברשימה משתנים, פיתוח נייטיב מזהה קריאות נפרדות ל-MovieOverview ויכול לעשות בהן שימוש חוזר.
MoviesScreen בקומפוזיציה כשאלמנט חדש נוסף לרשימה. מכיוון שלרכיבים הקומפוזביליים של MovieOverview יש מפתחות ייחודיים, פיתוח נייטיב מזהה אילו מופעים של 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בשני מופעים תהיה תמיד זהה. - אם מאפיין ציבורי מהסוג הזה משתנה, מערכת הקומפוזיציה תקבל על כך הודעה.
- גם כל סוגי המאפיינים הציבוריים הם יציבים.
יש כמה סוגים נפוצים וחשובים שכלולים בחוזה הזה, והקומפיילר של פיתוח נייטיב יתייחס אליהם כאל סוגים יציבים, גם אם הם לא מסומנים במפורש כיציבים עם ההערה @Stable:
- כל סוגי הערכים הפרימיטיביים:
Boolean,Int,Long,Float,Charוכו'. - מחרוזות
- כל סוגי הפונקציות (פונקציות למבדה)
כל הסוגים האלה יכולים לפעול לפי החוזה של המצב היציב כי אי אפשר לשנות אותם. מכיוון שהסוגים האלה אף פעם לא משתנים, הם גם לא צריכים להודיע על אף שינוי, ולכן קל יותר לעקוב אחרי החוזה הזה.
סוג אחד בולט שהוא יציב אבל ניתן לשינוי הוא הסוג MutableState
של פיתוח נייטיב. אם ערך מסוים נשמר ב-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 הוא ממשק, בדרך כלל יכול להיות שפיתוח נייטיב לא יחשיב את הסוג הזה כסוג יציב. כשמוסיפים את ההערה @Stable, מציינים לפיתוח נייטיב שהסוג הזה יציב, וכך מאפשרים למערכת להעדיף קומפוזיציות חכמות. זה אומר שפיתוח נייטיב יתייחס לכל ההטמעות כיציבות אם הממשק ישמש כסוג הפרמטר.
מומלץ בשבילך
- הערה: טקסט הקישור מוצג כש-JavaScript מושבת
- מצב ו-Jetpack פיתוח נייטיב
- תופעות לוואי בפיתוח נייטיב
- שמירת מצב ממשק המשתמש בפיתוח נייטיב