本文中提及的use
开头的函数,都出自与我的 ComposeHooks 项目,它提供了一系列 React Hooks 风格的状态封装函数,可以帮你更好的使用 Compose,无需关系复杂的状态管理,专心于业务与UI组件。
这是系列文章的第6篇,前文:
在上一次更新中,为了解决全局状态的管理问题,我们引入了新的钩子:useSelector
、useDispatch
它们的源码非常简单
@Composable
inline fun <reified T> useSelector(): T {
val map = useContext(context = ReduxContext)
return map.first[T::class] as T
}
@Composable
inline fun <reified A> useDispatch(): Dispatch<A> {
val map = useContext(context = ReduxContext)
return map.second[A::class] as Dispatch<A>
}
得力于 kotlin 的 inline
、reified
关键字,我们可以轻松的从store中取出我们的状态、以及dispatch函数。
但是有时我们并不需要整个状态对象,我们可能只需要其中部分成员属性,亦或者需要对状态中的某个属性进行变形映射。
说到这里不知道你有没有想到什么?还记得么你可能一直在kt文件中写Java代码?没错就是 run
映射
useSelector
我们只需要简单的构建一个重载函数,就可以让 useSelector
变得更好用:
@Composable
inline fun <reified T, R> useSelector(block: T.() -> R) = useSelector<T>().run(block)
现在我们继续对之前例子的代码进行改造:
@Composable
private fun SubSimpleDataStateText() {
/**
* 使用[useSelector]的另一个重载,你可以轻松的对状态进行变形,或者只取状态对象的部分属性作为你要关注的状态;
*/
val name = useSelector<SimpleData, String> { name }
Text(text = "User Name: $name")
}
@Composable
private fun SubSimpleDataStateText2() {
// age属性类型是Int,我们可以轻松的进行数据变形
val age = useSelector<SimpleData, String> { "age : $age" }
Text(text = "User $age")
}
useDispatch
dispatch 函数非常非常简单:typealias Dispatch<A> = (A) -> Unit
{ action: Any -> setState(reducer(state, action)) }
但是在异步场景,使用它有一点点麻烦,例如一个网络请求的场景:
val scope = rememberCoroutineScope() //获取协程作用域
TButton(text = "changeName") {
scope.launch {
// 这里在异步任务
delay(1.seconds)
val result = //....
dispatch(SimpleAction.ChangeName(result)) //dispatch
}
}
多多少少我们要写一点模板代码;
我们继续对 useDispatch
进行改造,增加一个异步版本的:
typealias DispatchAsync<A> = (block: suspend CoroutineScope.() -> A) -> Unit
@Composable
inline fun <reified A> useDispatchAsync(): DispatchAsync<A> {
val dispatch: Dispatch<A> = useDispatch()
val asyncRun = useAsync() //等同于 rememberCoroutineScope().launch {}
return { block ->
asyncRun {
dispatch(block())
}
}
}
改造后的 useDispatchAsync
函数将会返回一个异步版本的 dispatch 函数,函数闭包的返回值将会作为 Action 进行 dispatch 操作。
那么上边的模板代码将会变成:
val asyncDispatch = useDispatchAsync<SimpleAction>()
TButton(text = "Async changeName") {
asyncDispatch {
delay(1.seconds)
val result = //....
SimpleAction.ChangeName(result) //闭包的最后一行是返回值Action
}
}
如果你使用 retrofit ,并且已经使用协程改造了网络请求,你甚至可以将请求结果作为Action,那么这里将会进一步简化
一个极简的例子如下:
//网络状态的封装
sealed interface NetFetchResult {
data class Success(val data: String, val code: Int) : NetFetchResult
data class Error(val msg: Throwable) : NetFetchResult
data object Idle : NetFetchResult
data object Loading : NetFetchResult
}
// 极简的reducer
val fetchReducer: Reducer<NetFetchResult, NetFetchResult> = { _, action ->
action
}
// 注册到store
val store = createStore {
fetchReducer with NetFetchResult.Idle
}
// 组件中使用
@Composable
fun UseReduxFetch() {
val fetchResult: NetFetchResult = useSelector()
val dispatchAsync = useDispatchAsync<NetFetchResult>()
Column {
Text(text = "result: $fetchResult")
TButton(text = "fetch") {
dispatchAsync {
delay(2.seconds)
//网络请求结果
NetFetchResult.Success("success", 200) //这里替换成你的retrofit请求即可
}
}
}
}
好了以上就是 hooks 1.0.9 版本带来的一点小小改动,现在你的全局状态可以更加轻松的管理与使用了!
项目开源地址:junerver/ComposeHooks
MavenCentral:hooks
implementation("xyz.junerver.compose:hooks:1.0.9")
欢迎使用、勘误、pr、star。