Compose:长期副作用 + 智能重组 = 若智?(一)

简介: Compose:长期副作用 + 智能重组 = 若智?

笔者曾经写过一篇关于新手入坑Jetpack Compose的文章,其中谈到了rememberUpdateState的使用场景,但是最近的一次项目中还是踩坑了,而且收到了很多人反馈表示依然不理解如何正常使用这个Api,于是单独写一篇文章展开说说。

关于提到的文章传送门:妈!Jetpack Compose太难学了,别怕,这里帮你理清几个概念 - 掘金 (juejin.cn)

如果你完全不明白什么是智能重组副作用,可以先看看笔者写的这篇文章。


1.长期副作用


在Jetpack Compose的世界中,所谓的长期副作用基本就是等价于协程中的挂起函数,这个定义不一定对,但是足够覆盖绝大多数场景。

让我们看看一个长期副作用的样子:


@Composable
fun LongRunningSideEffectExample(){
    LaunchedEffect(Unit){
        delay(1000)
        // TODO: 我是长期副作用 
    }
}

可见,一个简单的长期副作用其实就是一个一段时间后才执行的逻辑,在大多数场景下,在delay结束后执行的逻辑都没有什么问题。


2.智能重组


众所周知,Jetpack Compose的编译器存在魔法,会在重组的时候,根据参数的是否发生了变化来决定是否充足当前的组件,这就是所谓的智能重组。

让我们看看一个智能重组的案例:


@Composable
fun RecompositionExample(
    text:String
){
    SideEffect {
        Log.d("重组记录","当前的值:$text")
    }
    Text(
        text=text
    )
}

SideEffectApi会在重组成功后调用lambda,因此我们可以通过观察日志来查看当前组件的重组时刻,通过实验得知,只有text参数发生变化的时候,SideEffect的lambda才会被执行,这就是所谓的智能重组,Compose会尽可能跳过没意义的重组。


3.长期副作用+智能重组=?


两者都是Jetpack Compose非常优秀的机制,但是两者在一起很容易出问题,例如下面这个组件:


@Composable
@Preview
fun LongRunningSideEffectWrongExample() {
    var count by remember {
        mutableStateOf(0)
    }
    Column {
        Button(onClick = { count++ }) {
            Text("当前的值:$count")
        }
        DelayOutputText(text = "$count")
    }
}
@Composable
fun DelayOutputText(
    text: String,
) {
    var delayOutputText by remember { mutableStateOf("") }
    LaunchedEffect(Unit) {
        delay(3000L)
        delayOutputText = text
    }
    Text("延迟输出的值:$delayOutputText")
}

组件非常简单,在出现DelayOutputText3秒后,尝试显示最新的text值,但是实际运行结果如下:

image.png

可见,3秒后并没有显示最新的值,而是显示初始化的值,不是说智能重组吗,怎么没重组,问题出在哪里了?

让我们回到LaunchedEffect本身的源码:


@Composable
@NonRestartableComposable
@OptIn(InternalComposeApi::class)
fun LaunchedEffect(
    key1: Any?,
    block: suspend CoroutineScope.() -> Unit
) {
    val applyContext = currentComposer.applyCoroutineContext
    remember(key1) { LaunchedEffectImpl(applyContext, block) }
}

LaunchEffect内部使用了一个remember来包裹LaunchedEffectImpl,总所周知,如果key没有发生变化,remember的lambda是不会重新被执行的,而我们通过LaunchedEffect传入的block参数,就在remember的lambda中,这导致了一个问题:

如果LaunchedEffect的key没有发生变化,LaunchedEffect内部的lambda拿到的block参数是旧的

回到上文提到的出问题的代码,

image.png

笔者框住的这个代码块,看似是3秒后用最新的text值赋值给delayOutputText,实际上这是一种思维误区,真实的情况则是:如果key没有发生变化的情况,即没有重启LaunchedEffect的情况下,lambda一直都是最初的那个实例,那个lambda实例取的text则是最初启动的时刻的值,因此3秒后,delayOutputText = text这段代码,实际上是将text第一次的值传给了delayoOutputText,后续的text值都被忽略了。

一切问题的根源是remember

remember忽视掉了新的lambda,最终执行的lambda都是最初那个,那么lambda内部的变量自然也是旧的了。

问题找到了,笔者想用一句经典的话来概括上述这段问题:

这不是一个bug,而是一个feature


相关文章
|
3月前
|
缓存 前端开发 JavaScript
【揭秘Rails高手都在用的秘密武器!】—— 资产管道:它是如何悄无声息地改变我们管理前端资源的方式?
【8月更文挑战第31天】资产管道是Ruby on Rails 3.1引入的特性,用于简化Web应用中CSS、JavaScript和图片等前端资源的管理和打包。它将静态资源集中管理并自动处理合并、压缩及版本控制,提升页面加载速度和用户体验。本文通过示例代码详细介绍了如何在Rails应用中配置和使用资产管道,包括创建目录结构、编写样式表和JavaScript文件以及在布局文件中引用静态资源。与传统方法相比,资产管道提供了更高效和自动化的解决方案,有助于提高开发效率和应用性能。
28 0
|
3月前
|
测试技术 编译器 持续交付
持续部署的内涵和实施路径问题之集成尽早进行每次集成很小的问题如何解决
持续部署的内涵和实施路径问题之集成尽早进行每次集成很小的问题如何解决
|
3月前
|
物联网 测试技术 持续交付
持续部署的内涵和实施路径问题之持续部署过程中需要控制过程成本并保持高效的问题如何解决
持续部署的内涵和实施路径问题之持续部署过程中需要控制过程成本并保持高效的问题如何解决
|
6月前
|
前端开发 JavaScript 编译器
摆脱无用代码的负担:TreeShaking 的魔力
摆脱无用代码的负担:TreeShaking 的魔力
摆脱无用代码的负担:TreeShaking 的魔力
|
API Kotlin 容器
Compose:长期副作用 + 智能重组 = 若智?(二)
Compose:长期副作用 + 智能重组 = 若智?
188 0
|
存储 安全 数据管理
OushuDB 小课堂丨孤立数据迫在眉睫的威胁:废弃文件如何毁掉您的业务
OushuDB 小课堂丨孤立数据迫在眉睫的威胁:废弃文件如何毁掉您的业务
79 0
|
安全 编译器 开发者
Compose 的重组会影响性能吗?聊一聊 recomposition scope
很多人担心Compose的性能, 其实Compose编译器通过大量优化保证了recomposition的范围尽可能小,使得compose即使频繁重绘也不会有性能问题
621 0
|
人工智能 数据可视化 数据挖掘
后疫情时代,用数据支持业务恢复创造新的可能性
2020年可以说每一天都在见证历史,新冠疫情的突然造访就如同“黑天鹅”不期而至,而企业现在还不开始数字化转型就如同“灰犀牛”存在潜在风险,当下在黑天鹅和灰犀牛的夹击下,经济和市场都产生了巨大的影响。
|
Kubernetes 安全 Devops
功能无法停止交付,遗留的技术债务问题怎么解决
如果你曾在一家高速增长的软件工程公司待过,你可能会听过类似这样的一段对话,是关于技术债务的:
[转]国地税合并对企业的影响大吗
2018年6月15日上午,全国各省(自治区、直辖市)级以及计划单列市国税局、地税局合并且统一挂牌。国地税自1992年分手之后,再次合并管理,对于我们企业来说有哪些影响呢?