在Compose中轻松使用异步dispatch管理全局状态

admin2024-04-03  2

写在前面

本文中提及的use开头的函数,都出自与我的 ComposeHooks 项目,它提供了一系列 React Hooks 风格的状态封装函数,可以帮你更好的使用 Compose,无需关系复杂的状态管理,专心于业务与UI组件。

这是系列文章的第6篇,前文:

  • 在Compose中使用useRequest轻松管理网络请求
  • 在Compose中使用状态提升?我提升个P…Provider
  • 在Compose中父组件如何调用子组件的函数?
  • 在Compose中方便的使用MVI思想?试试useReducer!
  • 在Compose中像使用redux一样轻松管理全局状态

useSelector、useDispatch 足够好用么?

在上一次更新中,为了解决全局状态的管理问题,我们引入了新的钩子:useSelectoruseDispatch

它们的源码非常简单

@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 的 inlinereified 关键字,我们可以轻松的从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
    }
}

使用新的 hook 改造你的 retrofit 请求获得全局状态

如果你使用 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。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明原文出处。如若内容造成侵权/违法违规/事实不符,请联系SD编程学习网:675289112@qq.com进行投诉反馈,一经查实,立即删除!