Android Compose UI状态管理之状态提升(二)

上文讲了一下关于compose状态的一些生命周期相关的基础知识,本文就说一下我对官方所说的状态提升的一些理解。 有兴趣的同学可以看下

原文: Android Compose UI状态管理之生命周期(一)

前言:什么是状态提升

您应将界面状态提升到读取和写入状态的所有可组合项之间的最低共同祖先实体 。您应使状态尽可能靠近其使用位置。通过状态所有者,向使用者公开不可变状态和事件,以修改状态。这是官方的说明,后面将通过例子来进行说明。

eg:写一个计数并显示的函数HelloContent1(),我们可能会这样写点击按钮文本加一

-------- demo1

kotlin 复制代码
//-------- demo1

@Composable
fun HelloContent1() {
    Log.d("tgw11", "HelloContent: ")
    var count by rememberSaveable { mutableStateOf(1) } //注释1
    //var count = mainViewModel.count.value  //注释2
    Column(modifier = Modifier.padding(16.dp)) {
        Text(
            text = "HelloContent1, $count",
            modifier = Modifier.padding(bottom = 8.dp),
        )
        Button(onClick = { count++ }) {
            Text(
                text = "点击计数增加, $count",
                modifier = Modifier.padding(bottom = 8.dp),
            )
        }

    }
}

上面这样写但就功能来说是没有问题的,但是假设count的值是一个公用的类似上面的注释2处。假设HelloContent()函数代码过多,您需要与其他可组合项共用界面元素状态,如有多个要使用到mainViewModel.count.value数值的函数方法,并在不同位置将界面逻辑应用到状态,则可在界面层次结构中提升状态所在的层次。这样做会使可组合项的可重用性更高,并且更易于测试管理。

kotlin 复制代码
@Composable
fun HelloContent() {
    Log.d("tgw1", "HelloContent: ")
    HelloContent1()
    ···
    省略n多代码
    ···
    HelloContent1()
    HelloContent1()
   
}

如何状态提升:

还是使用之前的例子如下:状态提升后为HelloContent1()的区别

-------- demo2

kotlin 复制代码
//-------- demo2

@Composable
fun HelloContent() {
    var count by rememberSaveable { mutableStateOf(1) }

    Log.d("tgw1", "HelloContent: ")
    //注释1
    HelloContent1(count = count, onCountChange = {
        count = it
    })
    
    //注释2
    Text(
        text = "Hello1",
        modifier = Modifier.padding(bottom = 8.dp),
    )
    
    //注释3
    Text(
        text = "Hello2",
        modifier = Modifier.padding(bottom = 8.dp),
    )
    
     Log.d("tgw1", "HelloContent: 值改变后--注释2---注释三发生重组")
}
kotlin 复制代码
@Composable
fun HelloContent1(count: Int, onCountChange: (Int) -> Unit) {
    Log.d("tgw11", "HelloContent: ")
    Column(modifier = Modifier.padding(16.dp)) {
        Text(
            text = "HelloContent1, $count",
            modifier = Modifier.padding(bottom = 8.dp),
        )
        Button(onClick = { onCountChange(count+1) }) {
            Text(
                text = "点击计数增加, $count",
                modifier = Modifier.padding(bottom = 8.dp),
            )
        }

    }
}

上面的HelloContent1()就是提升的状态,这样的话使HelloContent1()变为一个无状态的函数,具备了较强的通用性,也就是编程里面的的函数式方法

状态提升的通用模式就是

我们将状态提升到调用的地方,一般的状态提升是将状态变量替换为两个参数。

  • value: T:要显示的当前值
  • onValueChange: (T) -> Unit:请求更改值的事件,其中 T 是建议的新值

此值表示任何可修改的状态,比如计数器中的count变量,onValueChange只是一个方法名,根据上下文随意命名即可。

状态提升使用不当会造成一些没必要的重组

点击HelloContent1()中的button按钮更改count状态 比如-------- demo2中注释2,注释3,的text文本发生重组如下图:

实际上的话它没必要去重组,大量没必要的重组会消耗性能,解决方案一种是将注释2,注释3的text文本,有两种方案

  • 因为这里无状态可以将注释2,注释3的text文本写到一个compose的方法函数中。
  • 尽可能延迟读取(因为compose重组机制决定,状态读取发生在哪个函数哪个函数就会发生重组)

这里主要说一下什么是 延迟读取: 官方回答--发现性能问题后,延后读取状态会有所帮助。延后读取状态可以确保 Compose 在重组时重新运行尽可能少的代码。例如,如果界面的状态在可组合项树中向上提升,而您在可组合子项中读取状态,则可以将状态封装在 lambda 函数中。这种方式可以确保仅在实际需要时才会执行读取操作。

根据状态读取发生在哪个compose 函数作用域,状态更新的时候,哪个哪个compose 函数作用域就发生重组这一个特点,利用函数调用的特性去进行状态读取。

HelloContent()函数

kotlin 复制代码
    @Composable
    fun HelloContent() {
        var count by rememberSaveable { mutableStateOf(1) }

        Log.d("tgw1", "HelloContent: ")
 //注释1
        HelloContent3(count = { count } , onCountChange = {
            count = it
        })
         //注释2
        Text(
            text = "Hello1",
            modifier = Modifier.padding(bottom = 8.dp),
        )
         //注释3
        Text(
            text = "Hello2",
            modifier = Modifier.padding(bottom = 8.dp),
        )
        Log.d("tgw1", "HelloContent: 值改变后--注释2---注释三发生重组")

    }

HelloContent3()函数

kotlin 复制代码
@Composable
fun HelloContent3(count: () -> Int, onCountChange: (Int) -> Unit) {
    Log.d("tgw31", "HelloContent: ")

    Column(modifier = Modifier.padding(16.dp)) {
        Text(
            text = "HelloContent1, ${count()}",
            modifier = Modifier.padding(bottom = 8.dp),
        )
        Button(onClick = { onCountChange(count() + 1) }) {
            Text(
                text = "点击计数增加, ${count()}",
                modifier = Modifier.padding(bottom = 8.dp),
            )
        }

    }
}

与上面的HelloContent1()函数相比,我们的count参数接受一个带有返回值的方法,将count状态的读取,下降到HelloContent3()方法中

点击HelloContent3()中的button按钮更改count状态,发现整个HelloContent1()未发生重组如下图:

参考: developer.android.google.cn/jetpack/com...

相关推荐
生椰拿铁You几秒前
09 —— Webpack搭建开发环境
前端·webpack·node.js
狸克先生12 分钟前
如何用AI写小说(二):Gradio 超简单的网页前端交互
前端·人工智能·chatgpt·交互
baiduopenmap26 分钟前
百度世界2024精选公开课:基于地图智能体的导航出行AI应用创新实践
前端·人工智能·百度地图
loooseFish34 分钟前
小程序webview我爱死你了 小程序webview和H5通讯
前端
请叫我欧皇i1 小时前
html本地离线引入vant和vue2(详细步骤)
开发语言·前端·javascript
533_1 小时前
[vue] 深拷贝 lodash cloneDeep
前端·javascript·vue.js
闲暇部落1 小时前
‌Kotlin中的?.和!!主要区别
android·开发语言·kotlin
guokanglun1 小时前
空间数据存储格式GeoJSON
前端
zhang-zan1 小时前
nodejs操作selenium-webdriver
前端·javascript·selenium
猫爪笔记2 小时前
前端:HTML (学习笔记)【2】
前端·笔记·学习·html