iT邦幫忙

2021 iThome 鐵人賽

DAY 20
0
Mobile Development

認真學 Compose - 對 Jetpack Compose 的問題與探索系列 第 20

D20/ 怎麼在 compose 與 non-compoe 間傳資料 - Compose Side-Effect part 2

  • 分享至 

  • xImage
  •  

今天大概會聊到的範圍

  • rememberUpdateState

上一篇聊到,SideEffect 周邊還有一堆和 state & effect 相關的 API。今天想要聊聊 rememberUpdatedState

當今天我們要把某個資料/事件從 composable 傳出來的時候,我們可以用 callback 的形式將資料往外傳:

@Composable
fun MyComposable(callback: () -> Unit ) {

    // childBtn
    Button( onClick = { callback() }) {
        Text("click")
    }
}

在某些狀況,callback 可能會改變。但是 callback 在還沒被呼叫之前,其實都和這個 composable 無關:

@Composable
fun MyComposable(callback: () -> Unit ) {
    Button( onClick = { callback() }) {
        Text("click")
    }
}

@Compoable
fun ParentComposable() {
    Column {
        
        var count by remember { mutableStateOf(0) }
        var callback by remember { mutableStateOf({
            Log.d(TAG, "MyComposable: callback init")
            Unit
        }) }

        MyComposable(callback)
        

        // parentBtn
        Button(onClick = { count++ }) {
            Text("Change")
            callback = { Log.d(TAG, "MyComposable: callback $count") }
        }
    }
}

舉個例,假設有兩層的 Button,內層的 Button 點完之後會 log 一段文字。外層的 Button 每次點擊的時候都會更新內層 Button 點擊後 log 的文字。但因為 callback 是透過參數傳入,這個參數又有被 Button 用到。因此,每次 parentBtn 點擊一次,childBtn 都會觸發 recomposition,即便 MyComposable 其實根本沒有改變。

為了讓 MyComposable 不要觸發不必要的 recomposition,我們可以將 callback 給 remember 起來。remember 會在第一次 composition 執行時去運算 lambda 內的資料一次並存放起來,後面因為 Button 不是 reference 到 callback,而是 remember 後的結果,所以 callback 改變不會觸發 recomposition。

@Composable
fun MyComposable(callback: () -> Unit) {
    
    val callbackRemembered by remember { mtableStateOf(callback) }
    
    Button(onClick = { callbackRemembered() }) {
        Text("click")
    }
}

不對啊,remember 記起來了就不會再改了。這樣怎麼呼叫 callback 都會呼叫到同一個 callback 啊?這時候就是 rememberUpdatedState 發揮效果的時候了!

@Composable
fun MyComposable(callback: () -> Unit) {
    
    val callbackRemembered by rememberUpdatedState(callback)
    
    Button(onClick = { callbackRemembered() }) {
        Text("click")
    }
}

rememberUpdatedState 會永遠記錄最新的資料,但是透過 rememberUpdatedState 包裝後的資料 ( callbackRemembered ) 卻不會被視為修改,因此不會觸發 recomposition 。


SideEffect & rememberUpdateState

其實前面舉 Button 的例子不太好,因為 rememberUpdateState 最好被使用的時機點是當這個資料需要用在 SideEffect 之中的情況。舉個例:

@Composable
fun MyComposable(onDisposeCallback: () -> Unit) {
    
    val onDisposeRem by rememberUpdatedState(newValue = onDisposeCallback)
    
    DisposableEffect(Unit) {
        // do something
        
        onDispose {
            onDisposeRem()
        }
    }
}

假設我們要在 dispose 的時候做某件事情,但是這件事情需要外部的人提供參數(這裡是舉 callback 的例子,其實不一定是 callbck 的情境),這時我們就可以透過 rememberUpdatedState 將資料記住。

那如果我們不用 remember 呢?不能單純將參數傳入並且使用嗎?

@Composable
fun MyComposable(onDisposeCallback: () -> Unit) {
    
    DisposableEffect(Unit) {
        // do something
        
        onDispose {
            onDisposeCallback()
        }
    }
}

因為 Effect 會在首次 composition 時建立,並且在 key 值沒有修改之前不會重新建立 ( 此處的 key = Unit ),因此在 onDispose 時只會觸發第一次拿到的 onDisposeCallback。若要利用 key 值改變會重建 DisposeEffect 的機制,又會讓 Dispose lambda 和 onDispose lambda 沒必要的重複觸發。SideEffectLaunchedEffect 等其他的 side-effect API 也都有一樣的情況。

內部運作

其實仔細看看 rememberUpdatedState 其實也就是 remembermutableStateOf 的組合:

@Composable
fun <T> rememberUpdatedState(newValue: T): State<T> = remember {
    mutableStateOf(newValue)
}.apply { value = newValue }

差別在最後一行的 apply,當參數改變並觸發 recompose 時,rememberUpdatedState 會透過 apply 將 value 寫進 state 中,達到修改 state 的效果。


今天主要是研究了 rememberUpdatedState 這個工具。有點慶幸這次我是因為看了文件發現有這個東西,要是我是直接在遇到問題時需要這個東西,感覺一定會摸不著頭緒也不知道該從何查起。Side effect 和 state mangement 是 compose 中重要的大主題,感覺還有很多東西可以挖,希望之後可以多挖一點來和大家分享


Reference:


上一篇
D19/ 要權限的時後有 Launcher has not been initialized,怎麼辦? - SideEffect
下一篇
D21/ 怎麼結合 ViewModel 和 Compose? - ViewModel
系列文
認真學 Compose - 對 Jetpack Compose 的問題與探索30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
tomazwang
iT邦新手 5 級 ‧ 2022-09-20 13:45:33

一年後補充:其實這邊的用詞不太精準

SideEffect, LaunchedEffect 與 DisposableEffect 都是 Compose Effect API 的一員。文章中有時會把 "Effect API" 和 "SideEffect" 兩者混用

我要留言

立即登入留言