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

相关推荐
溪饱鱼1 分钟前
React源码阅读-fiber核心构建原理
前端·javascript·react.js
陈随易15 分钟前
Element Plus 2.10.0 重磅发布!新增Splitter组件
前端·后端·程序员
陈随易18 分钟前
2025年100个产品计划之第11个(哆啦工具箱) - 像哆啦A梦口袋一样丰富的工具箱
前端·后端·程序员
前端缘梦26 分钟前
微信小程序登录方案实践-从账号体系到用户信息存储
前端·微信小程序
用户214118326360227 分钟前
02-N8N教程-手把手教你用 PostgreSQL 实现 N8N 数据持久化,生产环境部署实战!
前端
玄玄子28 分钟前
webpack学习指南
前端·webpack·程序员
不爱说话郭德纲29 分钟前
面试官:你给我讲讲async/await
前端·深度学习·面试
前端小巷子31 分钟前
Promise 链式调用:让异步编程更优雅
前端·面试·promise
周日不上发条31 分钟前
前端必学:CSS实现精美渐变色票据组件(含完整源码)
前端
有梦想的攻城狮37 分钟前
从0开始学vue:pnpm怎么安装
前端·javascript·vue.js