Jetpack Compose——什么时候应该使用 derivedStateOf?

derivedStateOf — 一个普偏的问题的问题是什么时候什么位置去正确的使用此API?

这个问题的答案是 derivedStateOf {} 应该在你的状态或键的变化超过你想要更新你的UI 时使用。或者换句话说,derivedStateOf 就像 Kotlin Flows或其他类似反应式框架的 distinctUntilChanged。请记住,当可重组项的状态或key发生了变化,它们会进行重组。 derivedStateOf 允许您创建一个新的状态对象,它只根据您的需要进行更改。

让我们看一个例子。这里我们有一个用户名字段和一个在用户名有效时启用的按钮。

var username by remember { mutableStateOf("") }
val submitEnabled = isUsernameValid(username)

它一开始是空的,所以我们的状态是错误的。现在,当用户开始输入时,我们的状态会正确更新并且我们的按钮会启用。 

但这就是问题所在,因为我们的用户不断输入,我们正在不必要地一遍又一遍地向我们的按钮发送状态。

这就是 derivedStateOf 的用武之地。我们的状态变化超过了我们需要更新 UI 的次数,因此 derivedStateOf 可用于此以减少重组次数。 

var username by remember { mutableStateOf("") }
val submitEnabled = remember {
  derivedStateOf { isUsernameValid(username) }
}

让我们再次通过相同的示例来查看不同之处。

用户开始输入,但这次我们的用户名状态是唯一改变的。提交状态保持为真。当然,如果我们的用户名无效。我们的派生状态再次正确更新。

现在,这个例子有点过于简单了。在真实的应用程序中,Compose 很可能会跳过提交可组合项的重组,因为它的输入参数没有改变。

现实中,你需要用到derivedStateOf的情况很少,但是当你有类似的场景时,它可以非常有效地减少重组。

始终记住,输入参数和输出结果之间的变化数量需要有所不同,才有必要使用derivedStateOf。

一些可以使用derivedStateOf的示例(不是全部):

1、观察滚动是否通过阈值(scrollPosition > 0)
2、列表中的项数大于阈值(items > 0)
3、如上所述的表单验证(username.isValid())

现在,让我们看看关于derivedStateOf的其他一些常见问题。

1、是否有必要记住derivedStateOf ?

如果它在可组合函数中,则有必要。derivedStateOf就像mutableStateOf或任何其他需要在重组中保存下来的对象一样。如果在可组合函数中使用它,则应该将其包装在remember中,否则在每次重组时它都会被重新分配值。

@Composable
fun MyComp() {
  // We need to use remember here to survive recomposition
  val state = remember { derivedStateOf { … } }
}

class MyViewModel: ViewModel() {
  // 这里我们不需要remember(也不能使用它),因为ViewModel在Composition之外。
  val state = derivedStateOf { … }
}

2、remember(key)和derivedStateOf之间有什么区别?

用每个状态作为key键的remember和derivedStateOf乍一看似乎非常相似。 

val result = remember(state1, state2) { calculation(state1, state2) }
val result = remember { derivedStateOf { calculation(state1, state2) } }

remember(key)和derivedStateOf之间的区别在于重组的数量。当你的状态或键的变化多于你想要更新UI时,使用derivedStateOf{}。

例如,仅当用户滚动了LazyColumn时才启用按钮。

val isEnabled = lazyListState.firstVisibileItemIndex > 0

firstVisibleItemIndex会随着用户滚动而改变0,1,2等,每次改变都会导致读者重新组合。我们只关心它是否大于0。我们拥有的输入量和我们需要的输出量是不同的,因此这里使用了derivedStateOf来减少不必要的重组。

val isEnabled = remember {
derivedStateOf { lazyListState.firstVisibleItemIndex > 0 }
}

现在,假设我们有一个开销比较大的函数,它接收一个参数为我们返回计算后的一个结果。我们希望UI在函数的返回发生变化时重新组合(注意,函数也是幂等的)。在这里我们使用带有key的remember,因为我们的UI需要更新的次数和键的变化一样多。也就是说,我们有相同数量的输入和输出。

val output = remember(input) { expensiveCalculation(input) }

3、我是否需要同时使用remember(key)和derivedStateOf ?什么时候需要?

这就是事情变得有点棘手的地方。derivedStateOf只能在读取到Compose state object时更新。在derivedStateOf内部读取的任何其他变量将在创建derived state时捕获该变量的初始值。如果你需要在计算中使用这些变量,你可以把它们作为remember的key。通过一个例子,这个概念更容易理解,让我们以前面的isEnabled示例为例,并将其扩展一下,使其也具有一个启用按钮的阈值,而不是0。

@Composable
fun ScrollToTopButton(lazyListState: LazyListState, threshold: Int) {
  // There is a bug here
  val isEnabled by remember {
    derivedStateOf { lazyListState.firstVisibleItemIndex > threshold }
  }
  
  Button(onClick = { }, enabled = isEnabled) {
    Text("Scroll to top")
  }
}

这里我们有一个按钮,当列表滚动超过阈值时按钮可点。我们正确地使用了derivedStateOf来删除额外的重组,但是有一个错误。如果threshold参数改变,我们的derivedStateOf将忽略threshold参数的改变,因为derivedStateOf在创建时为所有非compose state object的变量赋予初始值。由于threshold是Int类型,因此将捕获传递给可组合对象的第一个值,并将其用于此后的计算。ScrollToTopButton仍然会重新组合,因为它的输入已经改变了,但是请记住,在重组过程中没有任何键缓存,它不会用新值重新初始化derivedStateOf。

我们可以通过查看代码的输出来了解这一点。一开始一切都很正常。

但是,一个新的的阈值(5)被传递到我们的组合时。 

即使我们的scrollPosition小于threshold, isEnabled仍然被设置为true。

这里的修复是将threshold添加为一个key,记住,这将在threshold发生变化时重新初始化我们的derivedStateOf状态。 

val isEnabled by remember(threshold) {
  derivedStateOf { lazyListState.firstVisibleItemIndex > threshold }
}

现在我们可以看到,当阈值改变时,isEnabled状态正确更新。

 4、我是否需要使用derivedStateOf来将多个状态组合在一起?

 大多数情况是不需要的。如果您有多个状态组合在一起以创建一个结果,那么您可能希望只要其中一个状态发生变化就可以进行重组。

以一个输入名字和姓氏并显示全名的表单为例。

var firstName by remember { mutableStateOf("") }
var lastName by remember { mutableStateOf("") }

// This derivedStateOf is redundant
val fullName = remember { derivedStateOf { "$firstName $lastName" } }

在这里,由于输出的变化和输入的变化一样多,所以derivedStateOf没有做任何事情,只是造成了很小的开销。derivedStateOf对异步更新也没有帮助,Compose状态快照系统是单独处理的,这里的调用是同步的。 在这种情况下,根本不需要额外的派生状态对象。

var firstName by remember { mutableStateOf("") }
var lastName by remember { mutableStateOf("") }

val fullName = "$firstName $lastName"

总结:

总而言之,请记住,当您的状态或键的更改多于您希望更新的UI时,将使用derivedStateOf。如果输入量和输出量没有差别,就不需要使用它。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值