在 Jetpack Compose 中扩展 useRequest 实现自定义数据处理、异常回滚

写在前面

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

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

在前面的文章中,我们简单的介绍过 useRequest 这个hook,他被设计的高度抽象,同时也极易扩展,在下面的两个章节中,我将举两个例子,让你在业务中更好的使用它

自定义数据处理、自定义异常

一般来说我们的后台数据都有一个统一的包装格式,大概这样:

@Serializable
data class BaseResp<T>(
  val data: T? = null,
  val status: Int,
  val message: String? = null
)

通常我们只关心我们的业务数据,也就是 data,如果直接使用 useRequest ,我们就需要在 UI 代码中进行解包装,这多少有点麻烦。

另一个就是后台的自定义错误类型,后台将接口报错进行更友好的包装,我们需要判断返回值的状态码 status 来确定业务是否错误,而不是简单的将数据填充到 useRequest 的 data 中。

我们只需要进行如下操作,即可扩展:

@Composable
fun  useAsyncRequest(
  requestFn: suspend (TParams) -> BaseResp<TData>, 
  optionsOf: RequestOptions<BaseResp<TData>>.() -> Unit = {},
): RequestHolder {
  val holder = useRequest(
    requestFn,
    optionsOf = optionsOf
  )
  val resp by holder.data 
  val reqErr by holder.error
    
  
  var myData by _useState(null)
  var myError by _useState(null)

  
  useEffect(resp, reqErr) {
    if (resp.asBoolean()) {
      if (resp.status == 200) {
        myData = holder.data?.data 
      } else {
        myError = BusinessErrors(resp.status, resp.message) 
      }
    }
    if (reqErr.asBoolean()) {
      myError = reqErr
    }
  }

  fun mutate(mutateFn: (TData?) -> TData) {
    myData = mutateFn(myData)
  } 

  return with(holder) {
    RequestHolder(
      data = myData, 
      isLoading = isLoading,
      error = myError,
      request = request,
      mutate = ::mutate, 
      refresh = refresh,
      cancel = cancel
    )
  }
}

这里的返回值并不需要与我一致,你如果不需要那么多函数完全可以自定义一个类型,或者使用 tuple 元组直接返回暴露

ps: 在后续版本,data、loading、error 将会转为 State State State,届时,你需要使用by来获取值

自定义插件扩展 useRequest 实现 mutate 回滚

之前我们介绍过,可以通过调用 mutate 函数实现乐观更新,乐观更新的概念我们不再复述.

如果乐观更新失败我们如何对数据回滚呢?

在直接使用 useRequest 的情况下,可以调用 usePrevious 来暂存 data 的上一个状态,在失败后调用 mutate 将上个状态回滚

例如一个修改用户名的场景 :

val (userInfoState, loadingState, _, _, mutate) = useRequest(
    requestFn = { NetApi.userInfo(it[0] as String) },
    optionsOf = {
        defaultParams = arrayOf("junerver")
    }
)
val userInfo by userInfoState
val previous by usePrevious(present = userInfo) 

Row {
    TButton(text = "changeName") {
        mockFnChangeName(input.value) 
        if (userInfo.asBoolean()) {
            
            mutate {
                it!!.copy(name = input.value)
            }
        }
        setInput("")
    }
    TButton(text = "rollback") {
        
        previous?.let { mutate { _ -> it } }
    }
}

previous?.let { mutate { _ -> it } } 这行代码可以放到 mockFnChangeNameonError 生命周期之下,这样在修改名称失败后就对乐观更新实施回滚

如果你有大量的乐观更新场景,每次都要写这么一堆代码,无疑是很麻烦的一件事,那么我们是否可以在每次请求成功之后保存成功状态,然后对外暴露一个函数,使用这个成功状态用作回滚。

完全可以,我们只需要写一个自定义插件就可以实现这一目标:

@Composable
private fun  useRollbackPlugin(ref: MutableRef<() -> Unit>): Plugin = remember {
    object : Plugin() {
        var pervState: FetchState? = null 

        
        fun rollback() {
            pervState?.let { fetchInstance.setState(it.asMap()) }
        }

        override val invoke: GenPluginLifecycleFn
            get() = { fetch: Fetch, options: RequestOptions ->
                initFetch(fetch, options) 
                object : PluginLifecycle() {
                    override val onMutate: PluginOnMutate
                        get() = {
                            pervState = fetch.fetchState 
                        }
                }
            }
    }.also { ref.current = it::rollback } 
}


@Composable
fun  useCustomPluginRequest(
    requestFn: suspend (TParams) -> TData,
    optionsOf: RequestOptions<TData>.() -> Unit = {},
): Tuple8, State<Boolean>, State, ReqFn, MutateFn, RefreshFn, CancelFn, RollbackFn> {
    val rollbackRef = useRef(default = { }) 
    val requestHolder = useRequest(
        requestFn = requestFn,
        optionsOf = optionsOf,
        plugins = arrayOf({
            useRollbackPlugin(ref = rollbackRef) 
        })
    )
    return with(requestHolder) {
        tuple(
            data,
            isLoading,
            error,
            request,
            mutate,
            refresh,
            cancel,
            eighth = { rollbackRef.current.invoke() } 
        )
    }
}

探索更多

好了以上就是 使用 hooks 的一些小小技巧,现在你可以自由的扩展 useRequest 来满足你对网络请求的个性化需求。

示例源码地址:MutateCustomPlugin

项目开源地址:junerver/ComposeHooks

MavenCentral:hooks2

本项目已经迁移到 Compose Multiplatform ,使用新的工件 id:hooks2

如果你在 CMP 依赖,直接使用:

implementation("xyz.junerver.compose:hooks2:2.1.0-alpha0")

如果你在 Android 环境依赖,请使用 id:hooks2-android

implementation("xyz.junerver.compose:hooks2-android:2.1.0-alpha0")

详细迁移说明请查看wiki

欢迎使用、勘误、pr、star。

阅读全文
下载说明:
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.shuli.cc/?p=21172,转载请注明出处。
0

评论0

显示验证码
没有账号?注册  忘记密码?