撰写:remember() with keys vs. derivedStateOf()

Compose: remember() with keys vs. derivedStateOf()

这两种方法有什么区别?

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

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

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

最大的一个是 derivedStateOf 不可组合,并且它自己不进行缓存(remember 可以)。因此 derivedStateOf 用于长 运行ning 计算,只有在键更改时才必须 运行。或者它可以用于合并不可组合的多个状态(例如在自定义 class 中)。

我认为对于“局外人”来说,确切的解释是模糊的,我们需要一些撰写团队成员的意见:)。我以上的来源是 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 = remember {
    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 运行time 和快照系统的作者,Chuck Jazdzewski 本人。我强烈推荐在这里和他一起看直播:https://www.youtube.com/watch?v=waJ_dklg6fU

编辑 2:

我们终于有了一些官方性能文档,其中很少提到 derivedStateOf。所以 derivedStateOf 的官方目的是限制组合数(就像在我的例子中一样)。 sauce

val result = remember(key1, key2) { computeIt(key1, key2) } re-calculates when key1 or key2 changes but derivedStateOf is for tracking a change in a or more State/MutableState as stated在文档中为

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