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对象,一个普通的对象是存在问题的,比如放在函数参数里面的普通类型Intage变量:

@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)
    }
}

例子中使用了derivedStateOfremember的配合:

  • 使用带参数的remember可以感知nameList对象的变化
  • derivedStateOf可以感知nameList内部元素变化

两者都会触发personList的改变,发生重组,解决了nameList可以变化的所有场景。

总结

监听状态变化有两种写法:

  1. 带参数的remember()方法:可以判断对象的重新赋值,derivedStateOf有局限性。
  2. derivedStateOf:可以自动跟随所依赖对象状态的变化,并可以感知到集合内部元素变化。

它们作用有一些重合性,但也有各种的局限性,某些场景需要把它们组合起来才可达到完美效果。

results matching ""

    No results matching ""