Jetpack Compose -> 无状态、状态提升,单向数据流

前言


上一章节我们讲解了重组作用域和 remember,本章我们来讲解下 Compose 的 『无状态』,状态提升,单向数据流;

无状态


所谓的无状态(Stateless)的这个状态指得是什么呢?就是控件的各种属性,比如 TextView 它就有一个状态属性,就是它的内容,我们可以通过 getText 和 setText 来分别获取设置它的内容,这个内容就是它的一个状态信息,而 Compose 是无状态的,是没有这种属性的,对应的Compose中的 Text()

ini 复制代码
var name = "Mars"
Text(text = name)

我们在设置这个name之后,我们是无法从其他地方拿到这个 name 的文字信息的,这个文字参数只是被 Text 拿来设置用来显示之后就扔了,后续想拿到是拿不到的,因为它根本就没有保存,这就是所谓的无状态;

这个无状态只能说是 Compose 这种声明式框架的一个特点,Compose 可以的无状态并不是绝对的,例如我们来看下面这个函数

kotlin 复制代码
@Composable
fun say() {
    var text = "Hello"
    Text(text)
}

这个函数中的 Text() 是无状态的,但是这个 say() 函数却是有状态的,它的里面有一个仅仅它自己能看到的字符串 "Hello";

所以 这里所说的无状态都是指的内部的无状态,例如 say() 这个 Compose 组件,它的内部的 Hello 是有状态的,但是,当我们调用这个 say() 的时候

scss 复制代码
setContent {
    say()
}

我们在 setContent 中是获取不到 say 的任何状态的;如果我们想获取这个内部状态的状态值应该怎么实现呢?

状态提升


例如我们想获取这个 text 的状态,我们需要将这个 text 放到say() 和 Text() 的外部,

kotlin 复制代码
setContent {
    var text = "Hello"
    say(text)
}

@Composable
fun say(value: String) {
    Text(value)
}

这样,我们就可以在 say() 的外部获取到这个状态了;这种在 Compose 中就叫作状态提升(State Hoisting)这个 Hoisting 就是提升,意思就是将状态提升到外部组件中;

同理,我们也只能在 setContent 中拿到这个状态,但是如果我们想在 setContent 的外部获取到这个状态,那么就需要将这个 text 提升到 setContent 的外部;

但是,这种状态提升,要尽量少的提升,最需要的地方提升即可;

这个时候,可能就会有人有疑问了,这种状态提升,不就导致调用麻烦了吗?我只想调用 say() 函数,结果现在需要定义一个变量传递进去,这个其实也好修改,我们可以给 value 定义一个默认值

kotlin 复制代码
@Composable
fun say(value: String = "Kobe") {
    Text(value)
}

这样就可以直接调用 say() 方法了;

无状态、状态提升的另一种特殊用法

我们接下来看另一个比较特殊的无状态、状态提升的用法,我们来看另一个组件 TextFiled 文字输入框

ini 复制代码
TextField(value = , onValueChange = ) //文字输入框

相当于原生的 EditText,它是 material 层的,不是 foundation 层,也不是 ui 层的,跟 Button 类似,是一个符合 material 风格的输入框,如果不想使用 material 风格,可以使用 BasicTextField 自己设计输入框风格;

它有两个参数,一个 value 一个 onValueChange,一个文字参数,一个文字改变监听数据变化,其中这个 value 就是提出来的参数,文字原本是内部的状态,现在提出来了,就成了输入的了,外部输入,就成了无状态的了;

java 复制代码
var name
TextField(name , onValueChange = ) //文字输入框

name 作为一个外部变量来充当这个无状态文本输入框的外部状态;

onValueChange 它是一个函数类型的表达式,我们可以写成

java 复制代码
var name
TextField(name , {}) //文字输入框

这种形式的,这个函数中要做的就是处理文字变化的事,也就是回调,我们来运行看下效果,这就是 material 风格的输入框

我们接下来输入几个数据看下:

可以看到,输入内容之后不显示,这是为什么呢?这是因为用户在输入新的内容之后并没有更新到 name 字段导致的,我们需要做如下更改:

ini 复制代码
var name = ""
TextField(name , onValueChange ={
    name = it
})

就是说 我们并没有把用户的输入行为和显示来源做关联,这就需要我们进行一个关联才行,但是这还不是我们最终的写法,因为我们是在 Compose 中,所以这些会变的变量我们不能直接写,需要用 mutableStateOf 和 remember 将它包裹起来才行

ini 复制代码
var name by remember {
     mutableStateOf("")
}
TextField(name , onValueChange = { newValue->
    name = newValue
})

这样,我们的修改才能生效,我们运行看下效果:

可以看到,我们的输入显示到了输入框中;对于这个 TextFiled Compose 并不会主动帮我们更新,而是需要我们手动更新,那么 Compose 为什么要这么做呢?

单向数据流


带着这个问题,我们先来聊聊数据,当我们既有缓存数据又有网络数据的时候,我们是如何将缓存数据和网络数据进行一个结合呢?第一次打开的时候,本地数据为空,从网络加载到数据之后显示并存到本地数据库,当加载下一页数据的时候,取到下一页数据合并到内存后显示,同时存到数据库,如果用户杀死app,重新打开,优先取数据库的数据同时从网络取数据,然后合并到内存,同时更新数据库;

那么问题来了,在这种双通道取数据的情况下,怎么保证数据的有效性呢?如何保证数据的同步性呢?

解决的本质就是:Single source of Truth(单一数据源),这样就不会出现数据冲突的问题了;让网络数据作为本地数据的上游,这样就不会出现数据不同步的问题了,这种方案也是被 Compose 官方建议的方案;这种单一数据源在 Compose 之前就已经被 Android 使用了,例如 Jetpack ViewModel 中的 Repository,它内部就是『数据库 + 网络』的形式;

Compose 所有会用到的界面数据都是单一数据源的方式,例如我们上面说的 TextFiled

ini 复制代码
var name by remember {
     mutableStateOf("")
}
TextField(name , onValueChange = { newValue->
    name = newValue
})

我们接着来看这段代码,如果我们想给输入框做一些限制,例如,限制输入框不能输入一些标点符号,那么我们就需要在 onValueChange 的回调中判断 newValue 的值是否符合规范,在不符合规范的情况下将用户输入的内容清除掉;

那么在这种情况下, Compose 就是通过单向数据流的方式来实现用户从 输入- 修改 - 显示 的过程的;整体数据从上往下传输,事件从下往上传输,一层一层的单向数据流传递;

好了,今天的讲解就到这里吧

下一章预告


状态机制的原理

欢迎三连


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

相关推荐
ChinaDragonDreamer2 小时前
Kotlin:2.0.20 的新特性
android·开发语言·kotlin
网络研究院4 小时前
Android 安卓内存安全漏洞数量大幅下降的原因
android·安全·编程·安卓·内存·漏洞·技术
凉亭下4 小时前
android navigation 用法详细使用
android
小比卡丘7 小时前
C语言进阶版第17课—自定义类型:联合和枚举
android·java·c语言
前行的小黑炭8 小时前
一篇搞定Android 实现扫码支付:如何对接海外的第三方支付;项目中的真实经验分享;如何高效对接,高效开发
android
落落落sss9 小时前
MybatisPlus
android·java·开发语言·spring·tomcat·rabbitmq·mybatis
代码敲上天.10 小时前
数据库语句优化
android·数据库·adb
GEEKVIP12 小时前
手机使用技巧:8 个 Android 锁屏移除工具 [解锁 Android]
android·macos·ios·智能手机·电脑·手机·iphone
model200514 小时前
android + tflite 分类APP开发-2
android·分类·tflite
彭于晏68914 小时前
Android广播
android·java·开发语言