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

2024-02-22 21:52

本文主要是介绍Jetpack Compose -> 重组作用域和remember(),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言


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

ReCompose Scope(重组作用域)


我们先来看一段代码

infoflow 2024-02-20 15-12-01.png

当我们将

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(重组作用域)

所以代码执行的问题就在

var name by mutableStateOf("老A")

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

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

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

remember


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

infoflow 2024-02-22 14-47-56.png

Creating a state object during composition without using remember

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

var name by remember { mutableStateOf("老A") } 

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

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

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

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 包裹,也就是,我们把变量的初始化放到了外面

infoflow 2024-02-22 15-06-45.png

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

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

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

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

infoflow 2024-02-22 18-29-52.png

这个还会导致编译失败;

带参数的 remember

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

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

例如:第一次 mars

第二次 mars

第三次 old a

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

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

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

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

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

下一章预告


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

欢迎三连


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

这篇关于Jetpack Compose -> 重组作用域和remember()的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/736608

相关文章

从状态管理到性能优化:全面解析 Android Compose

文章目录 引言一、Android Compose基本概念1.1 什么是Android Compose?1.2 Compose的优势1.3 如何在项目中使用Compose 二、Compose中的状态管理2.1 状态管理的重要性2.2 Compose中的状态和数据流2.3 使用State和MutableState处理状态2.4 通过ViewModel进行状态管理 三、Compose中的列表和滚动

为什么现在很多人愿意选择做债务重组?债重组真的就这么好吗?

债务重组,起初作为面向优质企业客户的定制化大额融资策略,以其高效周期著称,一个月便显成效。然而,随着时代的车轮滚滚向前,它已悄然转变为负债累累、深陷网贷泥潭者的救赎之道。在此路径下,个人可先借助专业机构暂代月供,经一段时间养护征信之后,转向银行获取低成本贷款,用以替换高昂网贷,实现利息减负与成本优化的双重目标。 尽管债务重组的代价不菲,远超传统贷款成本,但其吸引力依旧强劲,背后逻辑深刻。其一

docker-compose安装和简单使用

本文介绍docker-compose的安装和使用 新版docker已经默认安装了docker-compose 可以使用docker-compose -v 查看docker-compose版本 如果没有的话可以使用以下命令直接安装 sudo curl -L https://github.com/docker/compose/releases/download/1.16.1/docker-c

【C++】作用域指针、智能指针、共享指针、弱指针

十、智能指针、共享指针 从上篇文章 【C++】如何用C++创建对象,理解作用域、堆栈、内存分配-CSDN博客 中我们知道,你的对象是创建在栈上还是在堆上,最大的区别就是对象的作用域不一样。所以在C++中,一旦程序进入另外一个作用域,那其他作用域的对象就自动销毁了。这种机制有好有坏。我们可以利用这个机制,比如可以自动化我们的代码,像智能指针、作用域锁(scoped_lock)等都是利用了这种机制。

js私有作用域(function(){})(); 模仿块级作用域

摘自:http://outofmemory.cn/wr/?u=http%3A%2F%2Fwww.phpvar.com%2Farchives%2F3033.html js没有块级作用域,简单的例子: for(var i=0;i<10;i++){alert(i);}alert(i); for循环后的i,在其它语言像c、java中,会在for结束后被销毁,但js在后续的操作中仍然能访

Docker Compose使用手册

Docker Compose是一个比较简单的docker容器的编配工具,以前的名称叫Fig,由Orchard团队开发的开源Docker编配工具,在2014年被Docker公司收购,Docker Compose是使用Python语言开发的一款docker编配工具。使用Docker Compose,可以用一个yml文件定义一组要启动的容器,以及容器运行时的属性。Docker Compose称这些容器为

【docker】基于docker-compose 安装elasticsearch + kibana + ik分词器(8.10.4版本)

记录下,使用 docker-compose 安装 Elasticsearch 和 Kibana,并配置 IK 分词器,你可以按照以下步骤进行。此过程适用于 Elasticsearch 和 Kibana 8.10.4 版本。 安装 首先,在你的工作目录下创建一个 docker-compose.yml 文件,用于配置 Elasticsearch 和 Kibana 的服务。 version:

C语言作用域

作用域 (scope) 是描述程序可以访问标识符的区域。         一个标识符可以有块作用域、函数作用域、函数原型作用域、文件作用域和全局作用域。 1. 块作用域 (block scope)         块是一对花括号 {} 括起来的区域 或 函数体内任意复合语句定义范围内的区域。         定义在块中的变量具有块作用域。块作用域的变量的可见范围从定义处开始

Docker-Compose for Linux安装

Docker-Compose for Linux安装 1.前言2.安装Docker-Compose 1.前言 我们为什么要使用docker-compose? 我们运行一个docker镜像,需要添加大量的参数。 现在我们可以通过docker-compose编写这些参数。 Docker-Compose可以帮助我们批量管理这些容器。 我们只需要通过一个docker-compos

大话C++:第6篇 命名空间namespace作用域

1 命名空间概述 在一个大型的软件项目中,可能会有许多不同的代码文件,这些文件可能由不同的开发者编写,或者来自不同的库和模块。如果这些代码文件中存在同名的变量、函数、类或其他标识符,那么在编译或运行时就可能发生命名冲突,导致程序无法正确执行。 通过使用命名空间(namespace),开发者可以将相关的代码、变量、函数等组织在一起,形成一个独立的命名空间。这样,即使不同的代码片段中使用了相同的标