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...

相关推荐
崔庆才丨静觅11 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606112 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了12 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅12 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅12 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅13 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment13 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅13 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊13 小时前
jwt介绍
前端
爱敲代码的小鱼13 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax