Skip to content

SetValueFn改版

Junerver Hou edited this page Feb 16, 2025 · 10 revisions

在修改状态时,我们经常需要使用当前状态的值进行计算,将结果作为新值赋值给状态,这是一个非常常见的场景。

在过去我们使用 useGetState 时可以这样

val (state, setState, getState) = useGetState(default)
fun add() {
    setState(getState() + 1)
}

这里的 setState 就是一个 SetValueFn<T> 类型的函数,通过调用它传递一个新的值来变更状态,这很平常。

但是当我们的新状态值是基于旧状态值计算得出时,它看起来有一点不够优雅,需要调用getState() 来获取值(你也可以使用 state.value 他们是等效的)。

但是没办法,为了延时读取状态的值,避免不必要的触发重组,这是不得已的。

它与我们在 React 中的使用体验是不同的,在 React 中 useState 解构出来的 set 函数,既可以传递值也可以传递从上一个状态计算出来的函数。

在 Kotlin 2.1.0 中,我们终于可以获得和在 React 中一致的体验,现在 set 函数的签名从 SetValueFn<T> 变更为 SetValueFn<SetterEither<T>>。你的旧代码会因为类型推断报错,需要手动导入:

import xyz.junerver.compose.hooks.invoke

现在你可以这样使用:

import xyz.junerver.compose.hooks.invoke

val (state, setState) = useGetState(default)
fun set(num:Int) {
    setState(num) // 传递值
}
fun add(){
    setState{ it +1 } // 传递函数
}

这让我们的代码更加灵活!

获取setValue函数

除了手动导入函数以外:

import xyz.junerver.compose.hooks.invoke

如果在过去你有使用 set 函数来传递给组件,由于签名的变更,你需要调用 left()函数,比如过去的写法:

@Composable
private fun Copy() {
    val (state, setState) = useGetState("")
    val (copy, _) = useClipboard()
    Column {
        TextField(
            value = state.value,
            onValueChange = setState, // 直接将setState传参
            label = { Text("Text to copy") }
        )
        Button(onClick = { copy(state.value) }) {
            Text("Copy to clipboard")
        }
    }
}

现在你需要:

import xyz.junerver.compose.hooks.left

@Composable
private fun Copy() {
    val (state, setState) = useGetState("")
    val (copy, _) = useClipboard()
    Column {
        TextField(
            value = state.value,
            onValueChange = setState.left(), // 在解构出的 set 函数上调用 left 函数
            label = { Text("Text to copy") }
        )
        Button(onClick = { copy(state.value) }) {
            Text("Copy to clipboard")
        }
    }
}

left 函数很简单,它将 (Either<T, (T)->T>)->Unit 转成 (T)->Unit

旧代码迁移(kt 2.1.0)

只有在 Kotlin 2.1.0 才能自动识别到我们需要的扩展函数重载,如果你仍在使用 2.1.0 之前的 kotlin 版本,除了上面的迁移,你还需要将过去调用 setValue 的代码修改为使用 Either 容器传参,需要用到left()right() 这两个函数。

例如值类型传参,需要使用left()函数转换:

@Composable
private fun Paste() {
    val (state, setState) = useGetState("") // 需要迁移
    val (_, paste) = useClipboard()
    Column {
        Text("$state")
        Button(onClick = { setState(paste()) }) {
            Text("Paste from clipboard")
        }
    }
}

需要修改为:

import arrow.core.left
@Composable
private fun Paste() {
    val (state, setState) = useGetState("")
    val (_, paste) = useClipboard()
    Column {
        Text("$state")
        Button(onClick = { setState(paste().left()) }) { // 使用 `left` 函数将值转换成 Either 容器
            Text("Paste from clipboard")
        }
    }
}

使用旧值计算出新值的函数类型传参,需要使用right()函数:

val (state, setState, getState) = useGetState(0)
val async = useAsync {
    delay(1.seconds)
    setState(getState()+ 1)
}

修改为:

import arrow.core.right
val (state, setState) = useGetState(0)
val async = useAsync {
    delay(1.seconds)
    setState({ it: Int -> it + 1 }.right())
}

Clone this wiki locally