D20/ 怎麽在 compose 与 non-compoe 间传资料 - Compose Side-Effect part 2

今天大概会聊到的范围

  • 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:


<<:  [Day 19] 收集资料 — 你要对人家负责啊!

>>:  Day 19 - 相等判断与型别转换

[Lesson14] Retrofit

在 gradle (Module) 层级的 dependencies 中内加入: implement...

【Day 12】逻辑回归(Logistic Regression)(上)

步骤一:Function Set 昨天的最後我们提到我们要找一个事後机率(Posterior Pro...

铁人赛 Day9 -- 一定要知道的 CSS (六) -- background-color/background-image

前言 背景是一个如此重要的东西,你能想像萤幕的话棉全都是白底或黑底吗!!当然不行啊!! backgr...

[火锅吃到饱-10] 金大锄寿喜烧(烤)SUKIYAKI-台中复兴店

科技始终来自於人性 -- 「平板点餐APP」 第一次用平板点餐,是在日本京都的「啾啾烧肉」(じゅうじ...

Day24 ( 游戏设计 ) 记忆大考验

记忆大考验 教学原文参考:记忆大考验 这篇文章会使用「阵列」积木,建立两组灯号数据,搭配「函式」、「...