Skip to content

useRequest

Junerver Hou edited this page Sep 12, 2024 · 18 revisions

快速上手

useRequest 是一个功能丰富的异步数据管理的 Hooks,Compose 项目中的网络请求场景使用 useRequest 就够了。

useRequest 通过插件式组织代码,核心代码极其简单,并且可以很方便的扩展出更高级的功能。目前已有能力包括:

  • 自动请求/手动请求
  • 轮询
  • 防抖
  • 节流
  • 错误重试
  • loading delay
  • SWR(stale-while-revalidate)
  • 缓存

默认用法

suspend fun mockRequestArticle(): MockArticle {
    delay(2000L)
    return MockArticle(Clock.System.now().toEpochMilliseconds(), NanoId.generate(200))
}

val (data, loading, error) = useRequest(
    requestFn = {
        mockRequestArticle()
    }
)

你只需要将在 requestFn 参数赋值一个lambda闭包,在这个闭包中调用一个suspend修饰的异步函数即可

在组件初次加载时,会自动触发该函数执行。同时自动管理该异步函数的 loading , data , error 等状态。

基础用法

useRequest 的返回值是一个 七元组,它的类型如下:

Tuple7<TData?, Boolean, Throwable?, ReqFn, MutateFn<TData>, RefreshFn, CancelFn>
  1. data 异步数据
  2. loading 状态
  3. error 错误
  4. req 请求函数
  5. mutate 修改函数
  6. refresh 刷新函数
  7. cancel 取消请求函数

我们使用 kotlin 的解构语法可以轻松的获取他们。

req 请求函数是一个同步函数,它会使用当前组件的协程作用域,你只需要把他当作同步函数在组件中调用即可。

手动触发

useRequest 的第二个参数是 options,你可以通过这个参数完成各种请求状态管理的配置。

如果设置了 options.manual = true,则 useRequest 不会默认执行,需要通过 req 来触发执行。

import xyz.junerver.compose.hooks.invoke

val (data, loading, error, req) = useRequest(
    requestFn = {
        mockRequestArticle()
    },
    optionsOf {
        manual = true
    }
)

TButton(text = "manual request") { req() }

你可以使用顶层函数 optionsOf 来方便的创建选项,这个函数支持多个hooks,在本项目中所有需要设置配置的场合都可以是用这个函数

req函数的签名为:(Array<Any?>) -> Unit,你需要手动导入import xyz.junerver.compose.hooks.invoke,这样你就可以将它如同普通函数一样使用

默认参数

上面我们局的例子的异步请求是没有参数的,但实际场景大部分请求都需要传递参数,我们可以通过 options.defaultParams 来为异步请求设置默认参数:

useRequest(
    requestFn = { NetApi.userInfo(it[0] as String) },
    optionsOf {
        defaultParams = arrayOf("junerver")
    }
)

在闭包中我们可以通过 it[index] 对参数进行类型转换,如果你在 Jvm/Android 平台使用,我提供了一个方便的转换函数 asSuspendNoopFn

例如上面的例子,如果你可以写成这样:

useRequest(
    requestFn = NetApi::userInfo.asSuspendNoopFn(),
    optionsOf {
        defaultParams = arrayOf("junerver")
    }
)

生命周期

useRequest 提供了以下几个生命周期配置项,供你在异步函数的不同阶段做一些处理。

  • onBefore:请求之前触发
  • onSuccess:请求成功触发
  • onError:请求失败触发
  • onFinally:请求完成触发
useRequest(
    requestFn = { NetApi.userInfo(it[0] as String) },
    optionsOf {
        defaultParams = arrayOf("junerver")
        onBefore = {
            state += "onBefore: ${it.joinToString("")}"
        }
        onSuccess = { data, _ ->
            println("Lifecycle Lifecycle: onSuccess")
            state += "\n\nonSuccess:\nData:$data"
        }
        onError = { err, pa ->
            state += "\n\nonError: ${pa.joinToString("")}\nError: ${err.message}"
        }
        onFinally = { _, _, _ ->
            state += "\n\nonFinally!"
        }
    }
)

刷新(重复上一次请求)

useRequest 提供了 refresh 方法,使我们可以使用上一次的参数,重新发起请求。

假如在读取用户信息的场景中

  1. 我们读取了 ID 为 junerver 的用户信息 req("junerver")
  2. 我们通过某种手段更新了用户信息
  3. 我们想重新发起上一次的请求,那我们就可以使用 refresh 来代替 req("junerver"),这在复杂参数的场景中是非常有用的

mutate 立即变更数据

useRequest 提供了 mutate, 支持立即修改 useRequest 返回的 data 参数。

val (userInfo, loading, _, _, mutate) = useRequest(
    requestFn = { NetApi.userInfo(it[0] as String) },
    optionsOf {
        defaultParams = arrayOf("junerver")
    }
)

TButton(text = "changeName") {
    // 你可以在发起修改的同时,使用 mutate 进行乐观更新
    mockFnChangeName(newName)
    if (userInfo.asBoolean()) {
        mutate {
            it!!.copy(name = input)
        }
    }
}

mutate 函数的签名是:fun mutate(mutateFn: (TData?) -> TData)

取消响应

useRequest 提供了 cancel 函数,用于忽略当前异步请求返回的数据和错误

注意:调用 cancel 函数并不会立即停止 协程job 的执行(这涉及协程的取消机制)

同时 useRequest 会在以下时机自动忽略响应:

  • 组件卸载时,正在进行的 promise
  • 竞态取消,当上一次 promise 还没返回时,又发起了下一次 promise,则会忽略上一次 promise 的响应

Loading Delay

通过设置 options.loadingDelay ,可以延迟 loading 变成 true 的时间,有效防止闪烁。

这在一些快速反回结果的场景将会很有用,简单来说,只要接口响应时间小于你设置的 loadingDelayloading 状态将会保持 fasle

val (userInfo, loading, _, request) = useRequest(
    requestFn = { NetApi.userInfo(it[0] as String) },
    optionsOf {
        defaultParams = arrayOf("junerver")
        loadingDelay = 1.seconds
    }
)

轮询

通过设置 options.pollingInterval,进入轮询模式,useRequest 会定时触发 service 执行。

val (userInfo, loading) = useRequest(
    requestFn = { NetApi.userInfo(it[0] as String) },
    optionsOf {
        defaultParams = arrayOf("junerver")
        pollingInterval = 3.seconds
        pollingWhenHidden = true
        pollingErrorRetryCount = 5
    }
)
  • pollingInterval 轮询间隔时间
  • pollingWhenHidden 后台仍然发起轮询
  • pollingErrorRetryCount 轮询最大错误重试次数

Clone this wiki locally