如何防止 Jetpack Compose 中不必要的重组

How to prevent unnecessary recompositions in Jetpack Compose

我有一个用于搜索查询的 TextField 和一个将执行搜索的 Button,结果显示在一列中。由于搜索需要几秒钟 运行 我希望它在按下按钮而不是文本更改时执行。

这是一个简化的演示:

Column {
    val list = remember { mutableStateListOf<String>() }
    val textFieldValue = remember { mutableStateOf(TextFieldValue("")) }

    TextField(
        value = textFieldValue.value,
        onValueChange = { textFieldValue.value = it }
    )

    Button({
        list.clear()
        list.addAll(textFieldValue.value.text.split(""))
    }) {
        Text("Search")
    }

    list.forEach {
        println("test")
        Text(it)
    }
}

第一次按下按钮后,foreach 循环将 运行 更改文本。即使点击 TextField 也会重新 运行 循环。这不会 运行 搜索文本更改,但会重新呈现结果,这会导致在文本字段中键入时出现故障。

如何预防?

Column {
    val list = remember { mutableStateListOf<String>() }
    var textFieldValue = remember { mutableStateOf(TextFieldValue("")) }
    var searchTerm = remember { textFieldValue.value.text.copy() }

    TextField(
        value = textFieldValue.value,
        onValueChange = { textFieldValue.value = it }
    )

    Button({
        searchTerm = textFieldValue.value.text.copy()
        list.clear()
        list.addAll(searchTerm.text.split(""))
    }) {
        Text("Search")
    }

    list.forEach {
        println("test")
        Text(it)
    }
}

试试这个

以上内容适用于 Jetpack Compose。作者想了解有关 Compose Desktop 的信息,但它还不一样,因为它处于 alpha 阶段并且还没有进行太多优化。

修改 remember 值总是会导致所有视图的重组,即它的读取值。

Any changes to value will schedule recomposition of any composable functions that read value. documentation

停止它的方法是将所有读取 remember 值的视图移到一个单独的视图中。它将针对每个 remember 值更改进行重组,但不会影响容器。

在您的情况下非常简单:只需移动 TextField 并将 textFieldValue 传递到新函数中。您可以转发您需要的所有参数,例如 modifiertextStyle

@Composable
fun TestView(
) {
    Column {
        val textFieldValue = remember { mutableStateOf(TextFieldValue("")) }
        val list = remember { mutableStateListOf<String>("test") }

        TextField(textFieldValue)

        Button({
            list.clear()
            list.addAll(textFieldValue.value.text.split(""))
        }) {
            Text("Search")
        }

        list.forEach {
            println("test $it")
            Text(it)
        }
    }
}

@Composable
fun TextField(
    textFieldValue: MutableState<TextFieldValue>,
) {
    TextField(
        value = textFieldValue.value,
        onValueChange = { textFieldValue.value = it }
    )
}

我不确定为什么没有具有这种语义的系统函数,但在 compose 中他们更喜欢 State hoisting 模式来匹配 UDF。

我不喜欢移动 MutableState<> around as parameter 就像我从不使用 LiveData<> 作为参数一样。相反,您可以将阅读转化为 lambda:

@Composable
fun TextField(value: ()->TextFieldValue, 
              onValueChange(TextFieldValue)->Unit) {
    TextField(
        value = value(),
        onValueChange = { onValueChange(it) }
    )
} 
// call like
TextField(
        value = { textFieldValue.value },
        onValueChange = { textFieldValue.value = it }
)