【问题标题】:Compose: remember() with keys vs. derivedStateOf()撰写:remember() with keys vs. derivedStateOf()
【发布时间】:2021-11-28 14:22:38
【问题描述】:

这两种方法有什么区别?

  1. val result = remember(key1, key2) { computeIt(key1, key2) } (Docs)
  2. val result by remember { derivedStateOf { computeIt(key1, key2) } } (Docs)

两者如果key1key2 都没有改变,则避免重新计算 . 如果下游状态是派生的,第二个也避免了重新计算,但是,它们的行为是相同的,不是吗?

【问题讨论】:

  • 难道remember() 的键不必是States,而derivedStateOf() 必须是状态才能正常工作?

标签: kotlin android-jetpack-compose jetbrains-compose


【解决方案1】:

AFAIK 这里没有区别。两个构造在这里在这种情况下做同样的事情只是一个巧合。但是,还是有区别的!

最大的问题是derivedStateOf 是不可组合的,它自己没有缓存(remember 有)。所以derivedStateOf 用于长时间运行的计算,只有在密钥更改时才需要运行。或者它可以用于合并多个不可组合的状态(例如在自定义类中)。

我认为“局外人”的确切解释是模糊的,我们需要一些撰写团队成员的意见:)。以上我的来源是this one thread on slack和我自己的实验

编辑:

今天我学到了另一个derivedStateOf 的用法,非常重要。当使用一些非常常用的值进行计算时,它可以用来限制重组计数。

例子:

// we have a variable scrollState: Int that gets updated every time user scrolls
// we want to update our counter for every 100 pixels scrolled.
// To avoid recomposition every single pixel change we are using derivedStateOf
val counter = derivedStateOf {
    (scrollState / 100).roundToInt()
}

// this will be recomposed only on counter change, so it will "ignore" scrollState in 99% of cases
Text(counter.toString()).

我的来源尽可能直接 - 来自 compose runtime 和快照系统的作者 Chuck Jazdzewski 本人。我强烈推荐和他一起看直播:https://www.youtube.com/watch?v=waJ_dklg6fU

【讨论】:

  • 谢谢@Jakoss!我也觉得文档解释这有点太模糊了。
  • 虽然我喜欢“正常工作”的作曲,但应该更好地记录一些更“内部”的东西。但我确信 compose 会到达那里。致力于此的团队似乎非常投入
  • @FelixD。我添加了另一个用例,非常不错!
  • 但我猜val counter = derivedStateOf { (scrollState / 100).roundToInt() }应该被包裹在remember中:val counter = remember { derivedStateOf { (scrollState / 100).roundToInt() } }应该是
  • @user924 不,重点是不必如此。计算将在每次重组时执行,但只有在结果发生变化时才会触发重组
【解决方案2】:

val result = remember(key1, key2) { computeIt(key1, key2) }key1key2 更改时重新计算,但 derivedStateOf 用于跟踪文档中所述的一个或多个 State/MutableState 的更改

  var a by remember { mutableStateOf(0) }
    var b by remember { mutableStateOf(0) }
    val sum = remember { derivedStateOf { a + b } }
    // Changing either a or b will cause CountDisplay to recompose but not trigger Example
    // to recompose.
    CountDisplay(sum)

当您需要跟踪 State 对象的属性变化时,使用 derivedStateOf 很方便。您存储在 State 中的值可以是一个对象,但是当您需要跟踪对象的一个​​或某些属性时,您需要使用 derivedStateOf。如果它不是从State/MutableState 或具有带有@Stable 注释接口的对象派生的,Composable 将不会重新组合,因为重新组合需要更改状态。

例如,您需要在某个阈值或状态之后触发另一个重组的输入布局或项目数。

var numberOfItems by remember {
    mutableStateOf(0)
}

// Use derivedStateOf when a certain state is calculated or derived from other state objects.
    // Using this function guarantees that the calculation will only occur whenever one
    // of the states used in the calculation changes.
    val derivedStateMax by remember {
        derivedStateOf {
            numberOfItems > 5
        }
    }


Column(modifier = Modifier.padding(horizontal = 8.dp)) {

    Row(verticalAlignment = Alignment.CenterVertically) {
        Text(text = "Amount to buy: $numberOfItems", modifier = Modifier.weight(1f))
        IconButton(onClick = { numberOfItems++ }) {
            Icon(imageVector = Icons.Default.Add, contentDescription = "add")
        }
        Spacer(modifier = Modifier.width(4.dp))
        IconButton(onClick = { if (derivedStateMin) numberOfItems-- }) {
            Icon(imageVector = Icons.Default.Remove, contentDescription = "remove")
        }
    }

 
    if (derivedStateMax) {
        Text("You cannot buy more than 5 items", color = Color(0xffE53935))
    }
}

这是 whatsapp 文本输入,通过读取文本来根据文本是否为空来显示图标

internal fun ChatInput(modifier: Modifier = Modifier, onMessageChange: (String) -> Unit) {

    var input by remember { mutableStateOf(TextFieldValue("")) }
    val textEmpty: Boolean by derivedStateOf { input.text.isEmpty() }

    Row(
        modifier = modifier
            .padding(horizontal = 8.dp, vertical = 6.dp)
            .fillMaxWidth(),
        verticalAlignment = Alignment.Bottom
    ) {

        ChatTextField(
            modifier = modifier.weight(1f),
            input = input,
            empty = textEmpty,
            onValueChange = {
                input = it
            }
        )

        Spacer(modifier = Modifier.width(6.dp))

        FloatingActionButton(
            modifier = Modifier.size(48.dp),
            backgroundColor = Color(0xff00897B),
            onClick = {
                if (!textEmpty) {
                    onMessageChange(input.text)
                    input = TextFieldValue("")
                }
            }
        ) {
            Icon(
                tint = Color.White,
                imageVector = if (textEmpty) Icons.Filled.Mic else Icons.Filled.Send,
                contentDescription = null
            )
        }
    }
}

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-10-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多