在這個頁面中,您將瞭解可組合項的生命週期,以及 Compose 如何決定可組合項是否需要重組。
生命週期總覽
如管理狀態說明文件中所述,「組合」指的是應用程式的使用者介面,這個介面是透過執行可組合項所產生。Composition是描述您的UI的composable的樹狀結構。
當 Jetpack Compose 首次執行您的composable時,在初始composition期間,系統會追蹤您呼叫的composable,以在Composition內容中描述您的 UI。等到應用程式的狀態變更時,Jetpack Compose 就會安排重新composition。重新安排時,Jetpack Compose 重新執行可能為改變狀態而變更的composable,然後更新Composition以反映任何變更。
組合只能由初始組合產生,並透過重組程序進行更新。修改組合的唯一方式是透過重新組合。
圖 1。 Composition內Composable的生命週期。而這會進入Composition狀態,被compose 0 次以上,然後留下Composition的內容。
一般而言,您更改 State<T>
物件後才會觸發重新composition。然後Compose追蹤這些composable,並在Composition內容中讀取這個特定 State<T>
的composable,以及無法呼叫的可略過內容。
如果多次呼叫某個可組合項,系統就會將多個執行個體置入組合中。每個呼叫在Composition內容中都有各自的生命週期。
@Composable fun MyComposable() { Column { Text("Hello") Text("World") } }
圖 2. 代表Composition的 MyComposable
。如果多次呼叫某個可組合項,系統就會將多個執行個體置入組合中。不同顏色的元素代表獨立的執行個體。
Composition的composable的解剖
Composition的例項是由其呼叫網站識別。Compose 編譯器會將每個呼叫網站視為具有差異。呼叫多個呼叫網站的composable,用於建立Composition的多個composable執行個體。
如果在重新組合期間,可組合項呼叫的可組合項與先前組合時不同,Compose 會找出哪些可組合項已呼叫或未呼叫,而對於在兩個組合中都呼叫的可組合項,Compose 會避免重新組合這些可組合項,如果這些可組合項的輸入內容未變更。
保留身分是將副作用與composable建立關聯在一起的重要關係,這樣使用者才能順利完成工作,不必每次都開始重新composition。
請參考以下範例:
@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。每個呼叫都有專屬的呼叫網站和來源位置,編譯器將會用來識別該網路。
圖 3。 當狀態發生變更和重新composition時,composable中的 LoginScreen
代表。相同顏色則表示未經過重新composition。
雖然系統會優先呼叫 LoginInput
,但第二個呼叫仍會保留,但 LoginInput
執行個體會留存在composition中。此外,由於 LoginInput
的整個參數在整個參數中都發生過變更,因此 Compose 會略過對 LoginInput
的呼叫。
新增其他資訊以協助智慧重新設定
多次呼叫合成composition也會多次加到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 還會使用執行順序,讓執行個體在composable中保持不同。如果在清單「底部」加入新的 movie
,Compose 可重複使用已經存在組合中的執行個體,這是因為清單中的執行個體位置並未變更,因此這些執行個體都會輸入相同的 movie
。
圖 4。. 在清單底部加入新元素時,代表composition中的 MoviesScreen
。Composition中的 MovieOverview
composable可以重複使用。在 MovieOverview
中使用相同的顏色,表示可組合的未重新composable。
不過,如果因為在清單「頂端」或「中間」新增項目,或是因為移除/重新排序項目而使得 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。在清單中加入新元素時,代表compose中的 MoviesScreen
。無法重複使用 MovieOverview
composable,且所有副作用都會重新啟動。MovieOverview
中的不同顏色表示composable的compose。
在理想情況下,我們希望將 MovieOverview
的執行個體視為連結至該執行個體的 movie
身分。如果我們重新排序電影清單,最好會重新排列composition樹狀結構中的執行個體,而不是將不同的 MovieOverview
composable組成不同的電影執行個體。Compose 可讓您向執行階段指明您要用來識別樹狀結構中特定部分的值:key
composable的值。
透過呼叫由可傳入的一或多個值所發出的鍵來包裝程式碼區塊,系統會將這些值合併,以在composable中識別該執行個體。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
的個別呼叫,且可以重複使用。
圖 6。在清單中加入新元素時,代表compose中的 MoviesScreen
。由於 MovieOverview
元件含有專屬金鑰,因此 Compose 可辨識哪些 MovieOverview
執行個體並未變更,且可重複使用。他們的副作用會繼續執行。
某些composable已內建 key
composable。舉例來說,LazyColumn
接受在 items
DSL 中指定自訂 key
。
@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
等。 - 字串
- 所有函式類型 (lambdas)
所有這類型均能遵循穩定版的合約,因為這些變更無法變更。由於不可變更的類型一律不會改變,因此他們不用費心告知變更內容,因此您可以輕鬆遵循合約內容。
其中一個固定類型是穩定版,但可以成為 Compose 的 MutableState
類型。如果將值保存在MutableState
,整體狀態物件會視為穩定,因為當使用者撰寫程式碼變更時,
.value
屬性State
的 Google Ads 新帳戶重新申請驗證。
當所有已傳遞為組合的參數類型保持穩定時,系統會根據 UI 樹狀結構中的composable位置,比較參數值。如果上次呼叫後所有值都未變更,則系統會略過重新 composition。
只有在能夠證明類型時,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 支援智慧重新composition。也就是說,當介面做為參數類型使用時,Compose 會將所有實作項目視為穩定版。
為您推薦
- 注意:系統會在 JavaScript 關閉時顯示連結文字
- 狀態和 Jetpack Compose
- Compose 中的連帶效果
- 在 Compose 中儲存 UI 狀態