Jetpack Compose -> 重组作用域和remember()

前言


上一章我们讲解了 MutableState 和 mutableStateOf() 本章我们讲解下 remember 这个关键方法;

ReCompose Scope(重组作用域)


我们先来看一段代码

当我们将

csharp 复制代码
var name by mutableStateOf("老A")

lifecycleScope.launch{}

这两行代码放到 setContent 中的时候,它提示红色了,显然这样写,Compose 是不允许的(这里其实是可以运行的,但是运行之后没有任何的效果,也就是 name 的值在3s后并没有变成 "Mars"),那么这是为什么呢?

这里面,我们写的协程是没有任何问题的,它也正常的执行了,但是真正影响我们效果的是 mutableStateOf 提示的那行代码;

我们前面有说到,Compose 会在使用到这些变量的值改变的时候得到通知,从而进行重组(recompose) ,例如 Text(name) 它会在 name 变化的时候得到通知,进行重组,但是我们来具体想一下,它怎么被执行的呢?比如说:我们现在需要写一段代码,需要在运行的时候,从界面上指定一段代码去重复执行,例如重复执行 Text(name) 这行代码,那么应该怎么实现呢?基本思路就是拿到这行代码,然后执行它,但是这好像不太能实现,在 java 也好 kotlin 中也罢,几乎是不太能实现的;

但是在 Compose 中,它替我们进行了实现,Compose 的编译器插件会修改逻辑,它会把这些可能会被重新调用的代码块给包了起来,然后在这个被包起来的代码块执行完成之后,它会把这个代码块保存下来,并标记到当前它被执行的这个位置,去做个标记,当重新执行的条件达成的时候,比如说:当 name 的值发生改变的时候,Text(name) 所在的代码块就会发生 Recompose 具体来说这个所在代码块它会重新执行,并且执行的时候它所依赖的变量值就是最新的值,从而就能基于最新值组合出最新的界面;如果组合的结果和上一次的结果不同,那么布局和绘制流程也会重新再走一遍,从而有更新界面的显示,如果组合的结果和上次的结果相同,说明界面的实质内容没有改变,则不会重新绘制;

Compose 并不是包裹所有的代码,而是包裹需要包裹的代码,什么是需要包裹的代码?就是可能会发生变化的代码,例如 Text(name) 这个 name 是一个变量,这个 name 改变了,这个 Text 就需要重新执行,那么这个就是需要被包裹的代码;

也就是说 ReCompose 并不是全局的,而是哪里需要做才做,这种被包裹起来在 ReCompose 的时候一起执行的代码范围就被 Compose 称作 ReCompose Scope(重组作用域)

所以代码执行的问题就在

csharp 复制代码
var name by mutableStateOf("老A")

这行代码上,它重新执行的时候,并不是 Text(name) 这一行了,而是一个范围了,这个范围内的代码都会被重新执行了,自然也就包含了 name 的初始化过程,那么这就意味着当 Text(name) 执行的时候,它所用到的值已经不是刚才被协程修改过的值了("Mars"),而是一个被重新初始化的值了,也就是 "老A" 这个字符串,本质上不是赋值失败,而是赋值之后触发了 ReCompose,并且 ReCompose 的范围波及到了更多的代码,导致重新创建了一个新的 name 对象,并且 Text(name) 重新调用的时候,采用的是这个新的值;而那个旧的 name 确实被改成了 "Mars" 但是 Text 用的不是这个旧的 name 了,而是一个新的 name;

那么怎么解决呢?把 Text(name) 包裹起来就可以了

scss 复制代码
var name by mutableStateOf("老A")
// 这样包裹一下
Button(onClick = { /*TODO*/ }) {
    Text(text = name)
}
lifecycleScope.launch {
    delay(3000)
    name = "Mars"
}

remember


但是,我们在业务开发的时候,肯定是不能这么写的(业务场景也不允许嘛),那么我们还有其他的方法来规避吗?我们来看下 IDE 有没有给提供解决方案,我们把鼠标放到警告的地方看下:

Creating a state object during composition without using remember

我们没有使用 remember 来创建一个 state object,也就是需要我们使用 remember 来包一下这个 state object

csharp 复制代码
var name by remember { mutableStateOf("老A") } 

可以看到红线警告消失了;

remember 所做的事情就是:在它第一次执行的时候,它会执行 lambda 中的代码 {mutableStateOf("老A")} 同时呢,它也会保存这个结果,再次调用这个 remember 的时候,它就会直接返回这个保存的结果,remember 在这里起到了一个缓存的作用;

我们把 Button 包裹去掉看下效果:

scss 复制代码
setContent {
    Android_VMTheme {
        Surface {
            var name by remember { mutableStateOf("老A") } 
            Text(text = name)
            lifecycleScope.launch {
                delay(3000)
                name = "Mars"
            }
            // Ui()
        }
    }
}

可以看到,3s 后变成了『Mars』,也就是那个缓存的值被改成了 Mars,二次初始化拿到的是老的值;

所以说这个 remember 在 Compose 中是很有用的,它可以防止由于 ReCompose 而导致的预期之外的某些变量的反复初始化,这些反复初始化的变量可能会带来意料之外的结果,但是加一个 remember 这些问题就可以解决了;

但是我们在实际业务开发的时候,怎么去判断哪些变量会被 ReCompose 呢?我们是无法判断的,那怕这段逻辑开发这很清楚,但是一旦被外部调用的时候,它就不可控了;

所以 Compose 针对所有的 mutableStateOf() 都加上 remember 包裹;

这里有一点要注意:我们用 remember 包裹是为了防止被 Compose 包裹的代码执行 ReCompose 从而导致变量的反复初始化带来的意料之外的结果;

如果变量的初始化没有被 Compose 包裹,也就是,我们把变量的初始化放到了外面

可以看到,remember 上进行了红色警告

@Composable invocations can only happen from the context of a @Composable function

也就是说我们不能使用 remember 关键字,所以说,无论 Text(name) 怎样 ReCompose 都不会导致 name 变量的重新初始化;

因为 remember 是一个 Composable 函数,它只能在另一个 Composable 函数中被调用

这个还会导致编译失败;

带参数的 remember

kotlin 复制代码
@Composable
fun Request(value: String) {
    val length = remember {
        value.length
    }
    Text(text = "长度为$length")
}

如果我们每次调用这个 Request 方法的时候,传递的 value 值都是一样的,那么这段代码执行起来就没有问题,一旦我们传入的值不一样的时候,那么这个段代码执行就有问题了;

例如:第一次 mars

第二次 mars

第三次 old a

这个时候,如果使用了 remember 那么 length 的长度就不对了,

怎么解决这个问题呢? remember 提供了带参数的方法,这个带参数的意思是:虽然我可以缓存,但是我要给缓存加上一个或者几个 key,如果 key 和上次一样,我就用缓存的,如果不一样,我就不使用缓存了;

kotlin 复制代码
@Composable
fun Request(value: String) {
    val length = remember(value) {
        value.length
    }
    Text(text = "长度为$length")
}

这个意思就是 value 会影响 lambda 中的计算结果,如果 value 没有变就不计算了,直接返回结果,变了就执行计算逻辑;

好了,今天的内容就到这里吧~~

下一章预告


无状态,状态提升,单向数据流

欢迎三连


来都来了,点个关注点个赞吧,你的支持是我最大的动力

相关推荐
daifgFuture7 分钟前
Android 3D球形水平圆形旋转,旋转动态更换图片
android·3d
二流小码农2 小时前
鸿蒙开发:loading动画的几种实现方式
android·ios·harmonyos
爱吃西红柿!2 小时前
fastadmin fildList 动态下拉框默认选中
android·前端·javascript
悠哉清闲3 小时前
工厂模式与多态结合
android·java
大耳猫4 小时前
Android SharedFlow 详解
android·kotlin·sharedflow
火柴就是我4 小时前
升级 Android Studio 后报错 Error loading build artifacts from redirect.txt
android
androidwork6 小时前
掌握 MotionLayout:交互动画开发
android·kotlin·交互
奔跑吧 android6 小时前
【android bluetooth 协议分析 14】【HFP详解 1】【案例一: 手机侧显示来电,但车机侧没有显示来电: 讲解AT+CLCC命令】
android·hfp·aosp13·telecom·ag·hf·headsetclient
Chenyu_3106 小时前
09.MySQL内外连接
android·数据库·mysql
砖厂小工6 小时前
Kotlin Flow 全面解析:从基础到高级
android