當我們開始使用Jetpack Compose進行開發時,一開始可能會對可組合函數可以多次執行且順序任意感到有些不知所措。基本上,進入一個可組合函數就像走在岩漿上:你不能隨意運行任何程式碼,否則可能會導致連續彈出十個 Toast 提示框,或者每次螢幕閃爍都觸發分析事件。
為了避免這種混亂,Compose 提供了所謂的副作用。這些副作用是可控且安全的環境,我們可以在其中放置與介面繪製無關的邏輯,從而確保程式碼在我們需要的時候執行,而不是在框架決定重新組合元素時執行。
掌握非同步任務的 LaunchedEffect
如果你需要啟動一個協程,使其在元素顯示在螢幕上時一直運行,那麼LaunchedEffect 就是你的最佳選擇。這個函數會在可組合元素進入組合後立即啟動一個掛起的程式碼區塊,更重要的是,如果元素從螢幕上消失,它會自動取消該任務。
關鍵在於我們傳遞的鍵。如果某個鍵的值發生變化,效果就會被取消並從頭開始重新執行。這對於依賴變數的動畫,或者在使用者 ID 變更時從 API 載入資料的情況非常有用。但是,如果您希望某個效果在元件生命週期內只執行一次,則可以傳遞`Unit` 或 `true` 作為參數。
有時我們需要執行一個掛起函數,但我們並非在組合內部執行;相反,我們處理的是使用者事件,例如按鈕點擊。在這種情況下,我們不能使用 `LaunchedEffect`。解決方案是使用`rememberCoroutineScope`,它提供了一個與組合物件生命週期關聯的作用域,我們可以使用該作用域透過 `scope.launch` 手動觸發。
使用 rememberUpdatedState 避免不必要的重啟
有些棘手的情況是,我們需要處理一個長時間運行的效果,即使某些變數發生變化,我們也不希望它重新啟動。這時`rememberUpdatedState`就派上用場了。這個工具允許我們捕獲 lambda 函數或值的最新版本,而不會導致 `LaunchedEffect`重新啟動倒數或進程。
這在載入介面或啟動介面尤其有用。假設你有一個計時器,需要在時間結束時通知你;如果在計時器運行時回調函數發生了變化,你不再需要重置延遲,你只需要在計時器結束時執行函數的最新版本。
同步與清理:SideEffect 和 DisposableEffect
Compose 的角色不僅限於協程。有時,我們只需要 Compose 通知外部程式庫某些內容發生了變化。 SideEffect會在每次成功重新組合後執行。它是將 UI 狀態與非 Compose 管理的物件同步的理想工具,例如更新 Firebase 分析服務中的使用者屬性。
另一方面,有些效果會留下痕跡,需要清除以防止記憶體洩漏。為此,我們使用DisposableEffect。與其他效果不同,它要求我們在末尾添加一個onDispose程式碼區塊。這是註銷生命週期觀察者或在關閉設備感測器訂閱時防止記憶體洩漏的理想位置。
- 簡單同步: 副作用是更新外部狀態。
- 資源管理: 使用 DisposableEffect 來避免記憶體洩漏。
- 資料流: snapshotFlow 用於將 Compose 狀態轉換為 Kotlin 流程。
利用 derivedStateOf 和 produceState 進行狀態最佳化

當狀態不斷變化時,例如清單的滾動位置,可能會導致過多的重新組合,從而嚴重影響效能。為了解決這個問題,可以使用 derivedStateOf。它會建立一個派生狀態,該狀態僅在最終結果變更時才觸發重新組合,其作用類似於「直到變更才去重」過濾器。
用 `String` 連接兩個簡單的字串是一個常見的錯誤;在這種情況下,它會造成不必要的資源浪費。你應該將它用於基於閾值的計算,例如,僅當使用 `LazyColumn` 高效管理清單且第一個可見元素的索引大於零時才顯示「返回頂部」按鈕。
對於那些使用 LiveData 或 Flow 並希望將資料轉換為 Compose 可識別格式的使用者來說,`produceState` 是最佳選擇。它會啟動一個協程,該協程可以將值傳送到 State 物件。本質上,它封裝了資料檢索邏輯,並將其作為響應式狀態暴露出來,以便 UI 可以輕鬆觀察。
如果您在 ViewModel 層處理資料流,目前建議使用`collectAsStateWithLifecycle`。此功能優於基本資料收集,因為它能夠感知 Android 生命週期,在應用程式處於後台時停止收集,從而智慧地節省電池和記憶體。
完全掌控副作用意味著要懂得如何為每項任務選擇合適的工具,無論是將狀態封裝在容器類別中以方便測試,還是使用帶有 rememberSaveable的自訂儲存操作來防止裝置旋轉時資料遺失。歸根結底,關鍵在於維護單向資料流,並確保業務邏輯不會幹擾介面渲染過程。