前言
上一章节我们讲解了重组作用域和 remember,本章我们来讲解下 Compose 的 『无状态』,状态提升,单向数据流;
无状态
所谓的无状态(Stateless)的这个状态指得是什么呢?就是控件的各种属性,比如 TextView 它就有一个状态属性,就是它的内容,我们可以通过 getText 和 setText 来分别获取设置它的内容,这个内容就是它的一个状态信息,而 Compose 是无状态的,是没有这种属性的,对应的Compose中的 Text()
var name = "Mars"
Text(text = name)
我们在设置这个name之后,我们是无法从其他地方拿到这个 name 的文字信息的,这个文字参数只是被 Text 拿来设置用来显示之后就扔了,后续想拿到是拿不到的,因为它根本就没有保存,这就是所谓的无状态;
这个无状态只能说是 Compose 这种声明式框架的一个特点,Compose 可以的无状态并不是绝对的,例如我们来看下面这个函数
@Composable
fun say() {
var text = "Hello"
Text(text)
}
这个函数中的 Text() 是无状态的,但是这个 say() 函数却是有状态的,它的里面有一个仅仅它自己能看到的字符串 “Hello”;
所以 这里所说的无状态都是指的内部的无状态,例如 say() 这个 Compose 组件,它的内部的 Hello 是有状态的,但是,当我们调用这个 say() 的时候
setContent {
say()
}
我们在 setContent 中是获取不到 say 的任何状态的;如果我们想获取这个内部状态的状态值应该怎么实现呢?
状态提升
例如我们想获取这个 text 的状态,我们需要将这个 text 放到say() 和 Text() 的外部,
setContent {
var text = "Hello"
say(text)
}
@Composable
fun say(value: String) {
Text(value)
}
这样,我们就可以在 say() 的外部获取到这个状态了;这种在 Compose 中就叫作状态提升(State Hoisting)这个 Hoisting 就是提升,意思就是将状态提升到外部组件中;
同理,我们也只能在 setContent 中拿到这个状态,但是如果我们想在 setContent 的外部获取到这个状态,那么就需要将这个 text 提升到 setContent 的外部;
但是,这种状态提升,要尽量少的提升,最需要的地方提升即可;
这个时候,可能就会有人有疑问了,这种状态提升,不就导致调用麻烦了吗?我只想调用 say() 函数,结果现在需要定义一个变量传递进去,这个其实也好修改,我们可以给 value 定义一个默认值
@Composable
fun say(value: String = "Kobe") {
Text(value)
}
这样就可以直接调用 say() 方法了;
无状态、状态提升的另一种特殊用法
我们接下来看另一个比较特殊的无状态、状态提升的用法,我们来看另一个组件 TextFiled 文字输入框
TextField(value = , onValueChange = ) //文字输入框
相当于原生的 EditText,它是 material 层的,不是 foundation 层,也不是 ui 层的,跟 Button 类似,是一个符合 material 风格的输入框,如果不想使用 material 风格,可以使用 BasicTextField 自己设计输入框风格;
它有两个参数,一个 value 一个 onValueChange,一个文字参数,一个文字改变监听数据变化,其中这个 value 就是提出来的参数,文字原本是内部的状态,现在提出来了,就成了输入的了,外部输入,就成了无状态的了;
var name
TextField(name , onValueChange = ) //文字输入框
name 作为一个外部变量来充当这个无状态文本输入框的外部状态;
onValueChange 它是一个函数类型的表达式,我们可以写成
var name
TextField(name , {}) //文字输入框
这种形式的,这个函数中要做的就是处理文字变化的事,也就是回调,我们来运行看下效果,这就是 material 风格的输入框
我们接下来输入几个数据看下:
可以看到,输入内容之后不显示,这是为什么呢?这是因为用户在输入新的内容之后并没有更新到 name 字段导致的,我们需要做如下更改:
var name = ""
TextField(name , onValueChange ={
name = it
})
就是说 我们并没有把用户的输入行为和显示来源做关联,这就需要我们进行一个关联才行,但是这还不是我们最终的写法,因为我们是在 Compose 中,所以这些会变的变量我们不能直接写,需要用 mutableStateOf 和 remember 将它包裹起来才行
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
var name by remember {
mutableStateOf("")
}
TextField(name , onValueChange = { newValue->
name = newValue
})
我们接着来看这段代码,如果我们想给输入框做一些限制,例如,限制输入框不能输入一些标点符号,那么我们就需要在 onValueChange 的回调中判断 newValue 的值是否符合规范,在不符合规范的情况下将用户输入的内容清除掉;
那么在这种情况下, Compose 就是通过单向数据流的方式来实现用户从 输入- 修改 - 显示 的过程的;整体数据从上往下传输,事件从下往上传输,一层一层的单向数据流传递;
好了,今天的讲解就到这里吧
下一章预告
状态机制的原理
欢迎三连
来都来了,点个关注,点个赞吧,你的支持是我最大的动力~~