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:可以自动跟随所依赖对象状态的变化,并可以感知到集合内部元素变化。
它们作用有一些重合性,但也有各种的局限性,某些场景需要把它们组合起来才可达到完美效果。