Compose derivedStateOf和remember的使用
- derivedStateOf的使用
- remember的使用
- derivedStateOf和remember的优劣势
derivedStateOf的作用
derivedStateOf的作用:定义的对象状态依赖其他的对象状态时,需要使用derivedStateOf,当依赖对象状态发生改变,自己也可以跟着改变。
比如下面这个例子:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
var age by remember{ mutableStateOf(1) }
// age改变时person会自动刷新,引发Recompose
val person by remember{
derivedStateOf { "my age is $age" }
}
Column {
Button(onClick = { age += 1
}) {
Text(text = "click add age")
}
Text(text = person)
}
}
}
上述代码的效果:
点击按钮增加年龄age+=1,由于person变量使用了derivedStateOf,所以依赖age状态影响,当age变化了,person的数据也会跟着改变。而Text绑定了person状态,所以触发了Recompose,刷新Text。
remember的使用
那不用derivedStateOf,也可以实现,比如只借助带参数的remember的功能:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
var age by remember { mutableStateOf(1) }
// 参数的意义:如果参数变化了,就重新执行内部代码
val person = remember(age) {
"my age is $age"
}
Column {
Button(onClick = {
age += 1
}) {
Text(text = "click add age")
}
Text(text = person)
}
}
}
remember参数的意义:如果参数变化了,就重新执行内部代码。
remember的局限性
既然这么简单,那为什么还要有derivedStateOf呢,这是因为remember的参数功能的方法具有其局限性:
它比较的是对象的改变,通过equal的判定是具有缺陷的,比如一个对象集合只是内部元素改变,它会认为其对象没有改变,这不符合我们的期望。譬如下面这个例子:
setContent {
val nameList = remember {
mutableStateListOf("张三")
}
val personList = remember(nameList){
nameList.map {
"my name is $it"
}
}
Column {
Button(onClick = {
nameList.add("李四")
}) {
Text(text = "add new name")
}
for(person in personList){
Text(text = person)
}
}
}
运行发现,当我们点击增加新的姓名后刷新personList,从而没有触发重组,刷新Text文本显示。
我们肯定是希望集合元素改变,也是可以收到改变后的通知,这个时候derivedStateOf的作用就出来了,只需要改动下personList的实现就可以了:
val personList by remember {
derivedStateOf {
nameList.map {
"my name is $it"
}
}
}
需要注意的是,derivedStateOf能自动更新的一个条件是,内部的值必须是个State对象,一个普通的对象是存在问题的,比如放在函数参数里面的普通类型Int的age变量:
@Composable
fun updatePersonAge(age: Int, onClick: () -> Unit) {
// person不能收到age变化的更新
val person by remember {
derivedStateOf { "my age is $age" }
}
Column {
Button(onClick = {
onClick.invoke()
}) {
Text(text = "click add age")
}
Text(text = person)
}
}
如果改成State,就可以了:
fun updatePersonAge(age: State<Int>, onClick: () -> Unit){....}
但这么写不是很好,作为一个函数来说,最好是用一个通用类型来作为参数使之更通用。
remember的优势
这个时候反而用带参数的remember是更好的选择:
@Composable
fun updatePersonAge(age: Int, onClick: () -> Unit) {
val person = remember(age) {
"my age is $age"
}
Column {
Button(onClick = {
onClick.invoke()
}) {
Text(text = "click add age")
}
Text(text = person)
}
}
那如果参数是个List是不是也可以呢?理解了最开始说的带参数remeber的局限性,就会知道也还是有问题的:
@Composable
fun updateNameList(nameList: List<String>, onClick: () -> Unit) {
// nameList内部元素变化,不会触发personList的改变
val personList = remember(nameList) {
nameList.map {
"my name is $it"
}
}
Column {
Button(onClick = {
onClick
}) {
Text(text = "add new name")
}
for (person in personList) {
Text(text = person)
}
}
}
derivedStateOf的优势
运行后,和预期一样是不能生效的,nameList内部元素变化,不会触发personList的改变。那改成derivedStateOf不就行了?
@Composable
fun updateNameList(nameList: List<String>, onClick: () -> Unit) {
// nameList内部元素变化,可以触发personList的改变
val personList by remember {
derivedStateOf {
nameList.map {
"my name is $it"
}
}
}
Column {
Button(onClick = {
onClick.invoke()
}) {
Text(text = "add new name")
}
for (person in personList) {
Text(text = person)
}
}
}
是可以的,如果是内部元素改变确实可行。比如这么调用该方法:
updateNameList(nameList = nameList) { nameList.add("李四") }
derivedStateOf的局限性
但前面说了作为函数参数时使用derivedStateOf存在问题吗?这种List的场景,不会有问题吗?
确实还是会存在问题的,比如我们每次都去创建一个新的List替换之前的,就发现并不会生效:
updateNameList(nameList = nameList) { nameList = mutableStateListOf("张三", "李四") }
这两种方式对于参数是List这种的场景,都有局限性。迷惑了,那该如何处理?
derivedStateOf和remember的配合
参数是List这种的场景,使用derivedStateOf无法正常使用的根本原因是:不带参数的remember无法感知到nameList的整个变量的变化,所以不能更新。
那结合使用带参数的remember不就可以了!试一下看看:
fun updateNameList(nameList: List<String>, onClick: () -> Unit) {
// nameList内部元素变化,可以触发personList的改变
// 使用带参数的remember解决:感知nameList的变化
val personList by remember(nameList) {
derivedStateOf {
nameList.map {
"my name is $it"
}
}
}
Column {
Button(onClick = {
onClick.invoke()
}) {
Text(text = "add new name")
}
}
for (person in personList) {
Text(text = person)
}
}
例子中使用了derivedStateOf和remember的配合:
- 使用带参数的
remember可以感知nameList对象的变化 derivedStateOf可以感知nameList内部元素变化
两者都会触发personList的改变,发生重组,解决了nameList可以变化的所有场景。
总结
监听状态变化有两种写法:
- 带参数的remember()方法:可以判断对象的重新赋值,derivedStateOf有局限性。
- derivedStateOf:可以自动跟随所依赖对象状态的变化,并可以感知到集合内部元素变化。
它们作用有一些重合性,但也有各种的局限性,某些场景需要把它们组合起来才可达到完美效果。