新改进
在 Kotlin 2.1.0 版本引入了一个新的改进:Improved overload resolution for functions with generic types
官方描述,是对泛型函数的重载解析进行了加强,例如在过去:
kotlin
class KeyValueStore<K, V> {
fun store(key: K, value: V) {} // 1
fun store(key: K, lazyValue: () -> V) {} // 2
}
fun <K, V> KeyValueStore<K, V>.storeExtension(key: K, value: V) {} // 1
fun <K, V> KeyValueStore<K, V>.storeExtension(key: K, lazyValue: () -> V) {} // 2
fun test(kvs: KeyValueStore<String, Int>) {
// Member functions
kvs.store("", 1) // Resolves to 1
kvs.store("") { 1 } // Resolves to 2
// Extension functions
kvs.storeExtension("", 1) // Resolves to 1
kvs.storeExtension("") { 1 } // Doesn't resolve
}
对于类中泛型函数的重载是可以正确解析的,你传递一个lambda作为参数不会被解析到 函数1
,但是在扩展函数的情况下,编译器会报错提示你存在歧义的重载函数,它无法将上边例子中的 lambda 解析到 storeExtension(key: K, lazyValue: () -> V)
在 Compose 中实现 React 的 useState?
这个问题对于我们日常开发可能影响不大,但是对于库作者而言可能有些难受。例如在 ComposeHooks 中,我一直期望实现类似 React 中 useState
的效果,即 setState
函数既能接收值,也能接收一个 (oldStateValue)=>newStateValue
函数,因为这两者在逻辑上是几乎是一致的,你只需要对 lambda 求值,就可以使用同样的逻辑来执行更新状态。
在 React 中你可以这样写:
javascript
const [state,setState] = useState(0);
setState(1); // 更新状态值为1
setState(value => value + 1); // 更新状态值加1
这种写法非常灵活,我们经常需要使用当前状态的值进行计算,将结果作为新值赋值给状态,这是一个非常常见的场景。
这一切都是因为 JavaScript 是弱类型语言,也就是解构出的 setState
既可以接收一个函数作为参数,也可以接收一个值作为参数,但是在 Kotlin 中无法实现 Kotlin 也不支持联合类型,但是这个新特性让我看到了转机。
Arrow 前来助力
arrow 是一个非常棒的 Kotlin 函数式编程库,它扩展了 Kotlin 在函数式编程上的应用场景,在 ComposeHooks 中也有很多地方运用了这个库。
这里我们主要使用的是 Either<L,R>
这个容器类型,这个类型包装了左值与右值,其结果只能是其中之一,非常类似 Kotlin 原生的 Result
类型,它提供了方便的扩展函数 left()
与 right()
来快速的创建 Either
实例。
回到正题,我们的 setState 函数其实是这样的一个函数:
kotlin
typealias SetValueFn<T> = (T) -> Unit
现在它只能接收一个 值 作为参数,而不能接收一个 lambda,那如果这里的 T
的类型是这样的呢:
kotlin
Either<T, (T) -> T>
它是一个容器类型,它的左值是值,右值是一个函数,这时我们只需要为这个函数类型的调用操作符进行重载:
kotlin
typealias SetterEither<T> = Either<T, (T) -> T>
operator fun <T> SetValueFn<SetterEither<T>>.invoke(leftValue: T) = this(leftValue.left())
operator fun <T> SetValueFn<SetterEither<T>>.invoke(rightValue: (T) -> T) = this(rightValue.right())
然后借助 Either
的 fold
函数我可以方便的处理左右值:
kotlin
GetStateHolder(
state = state,
setValue = { value: SetterEither<T & Any> ->
val newValue = value.fold({ it }, { it(state.value) }) // 处理左右值情况
state.value = newValue
},
getValue = { state.value }
)
左值直接取值,右值传递当前状态的值执行 lambda 进行求值。
现在你只需 import
这个 invoke
重载,就可以获得在 React 中使用 useState
一样的体验了。
kotlin
import xyz.junerver.compose.hooks.invoke
val (state, setState) = useGetState(default)
fun set(num:Int) {
setState(num) // 传递值
}
fun add(){
setState{ it +1 } // 传递函数
}