一文详解VueUse中useAsyncState的实现原理

 更新时间:2024年01月12日 08:38:27   作者:丶远方  
在Vue 3 Composition API中,我们可以使用自定义钩子函数来封装可复用的逻辑,useAsyncState是一个用于管理异步状态的自定义钩子函数,本文将给大家介绍一下useAsyncState的实现原理,感兴趣的朋友可以参考下

useAsyncState是一个用于管理异步状态的自定义钩子函数。它是你简化异步操作的最佳拍档,就像鱼儿离不开水,雄鹰离不开天空,你老婆离不开你,同时异步也离不开 useAsyncState,它简化了在Vue组件中处理异步操作的过程,如发送网络请求、加载数据或执行其他耗时的任务。

背景

在Vue 3 Composition API中,我们可以使用自定义钩子函数来封装可复用的逻辑。useAsyncState是一个强大而灵活的自定义钩子函数,帮助我们管理异步操作的状态,使代码更简洁、可读性更强。

我们先来看一下我们平时在项目开发的过程中如何使用异步状态:

<template>
  <div>{{ data }}</div>
</template>

<script lang="ts" setup>
import { ref, onMounted } from 'vue'
import { getData } from './api'

const data = ref(null)
const loading = ref(false)
function loadData() {
  loading.value = true
  getData()
    .then(res => {
      data.value = res
    })
    .catch(e => {
      console.log(e)
    })
    .finally(() => {
      loading.value = false
    })
}

onMounted(() => {
  loadData()
})
</script>

在上面的代码中,我们可以看到,我们需要定义一个data变量来存储异步操作的结果,还需要定义一个loading变量来存储异步操作的加载状态,还需要定义一个loadData函数来执行异步操作,当异步操作开始时,我们需要将loading设置为true,当异步操作结束时,我们需要将loading设置为false,当异步操作成功时,我们需要将结果赋值给data,在这段代码中,我们需要定义两个变量和一个函数,来处理异步操作,这样的代码显然不够优雅,我们可以通过自定义钩子函数来优化它。

针对上面的代码,我们不难发现,当我们需要获取异步状态时,需要一个变量来接受获取到的异步状态,有时我们也会需要用到loading属性来判断异步操作是否正在加载,为此我们需要完成 useAsyncState 来实现它的功能,我们下来看一下实现后的代码:

<template>
  <div>{{ data }}</div>
</template>

<script lang="ts" setup>
import { useAsyncState } from './useAsyncState'
import { getData } from './api'
const { data, loading } = useAsyncState(getData, null) // 传递异步操作的函数和初始值
</script>

对此我们使用短短十行代码,就实现了异步操作的状态管理,比起之前的代码,我们可以看到,我们不需要定义额外的变量和函数,只需要调用useAsyncState函数,就可以获取异步操作的状态,这样的代码更加简洁,可读性更强。

目的

useAsyncState 旨在提供以下功能:

  • 方便管理异步操作的状态
  • 处理异步操作的加载中、成功和错误等不同状态
  • 支持自定义操作,如在成功或失败时执行其他逻辑
  • 提供性能优化选项,避免不必要的更新

基础设计

在实现useAsyncState之前,我们先来初步的设计一下它的结构:

参数

名称描述类型必传默认值
fn用于执行异步操作的函数,该函数返回一个 Promise,在调用返回值 execute 时将会执行该函数,将 Promise 结果赋值给 state(...args) => Promise<any>-
initialValue默认值,fn函数未执行完成之前,state将为默认值,该参数的类型应和 fn 参数返回的 Promise 结果类型相同Awaited<ReturnType>-

返回值

名称描述类型
state异步操作的状态,初始值为"initialValue",当 fn 返回的Promise状态完成时,将结果赋值给 stateAwaited<ReturnType>
loading异步操作是否正在加载中,初始值为false,当 fn 执行时,状态为loadin为true,Promise状态完成时,loading为falseboolean
execute执行异步操作的函数,调用时会执行 fn 函数,重新获取异步状态() => void

注:我们最初的设计是为了方便大家的理解,只实现基本的功能,后续我们会对其进行改进,使其更加易用。

实现

有了上面的设计,我们就可以开始实现useAsyncState了。首先,我们需要定义一个useAsyncState函数,该函数接受两个参数,分别是fninitialValue,并返回一个对象,该对象包含多个属性。

import { ref, Ref } from 'vue'

interface UseAsyncStateReturnType<T> {
  state: Ref<T>
  loading: Ref<boolean>
  execute: () => void
}

/**
 * 响应式异步状态管理
 * @param {() => Promise<T>} fn
 * @param {T} initialValue
 * @returns {UseAsyncStateReturnType<T>}
 */
export function useAsyncState<T>(
  fn: () => Promise<T>,
  initialValue: T
): UseAsyncStateReturnType<T> {
  // 用于保存异步状态的 ref,默认为初始值
  const state = ref<T>(initialValue)
  // loading 状态,默认为 false
  const loading = ref<boolean>(false)

  async function execute() {
    // 将 loading 状态设置为 true
    loading.value = true
    try {
      const data = await fn()
    } finally {
      // 将 loading 状态设置为 false
      loading.value = false
    }
  }

  return {
    state,
    loading,
    execute
  }
}

在上面的代码中,我们实现了 useAsyncState 的基本功能,它接受两个参数,分别是 fn 和 initialValue,并返回一个对象,该对象包含我们约定的属性。

此时我们的 useAsyncState 就已经完成了,我们可以在组件中去使用它

<template>
  <div>
    <div v-if="loading">Loading...</div>
    <div v-else-if="state">Data: {{ state }}</div>
    <div v-else>Error</div>
    <button @click="execute">Execute</button>
  </div>
</template>

<script lang="ts" setup>
  import { useAsyncState } from './useAsyncState'

  const { state, loading, execute } = useAsyncState(
    () => new Promise(resolve => setTimeout(() => resolve('data'), 1000)),
    []
  )

  execute()
</script>

在使用过程中,我们发现useAsyncState还有一些不足之处:

  • 需要手动调用execute函数,才能执行异步操作
  • 调用 execute 函数时,无法传递参数
  • 无法处理异步操作的错误
  • 如果我们需要再响应成功和失败的情况下,执行不同的操作,就需要在execute函数中添加额外的逻辑

为了解决上面的问题,我们可以对useAsyncState进行改进,使其更加易用。

改进

功能改进

我们针对上面的问题,梳理一下我们的解决方案:

  • 添加 immediate 参数,用于控制是否立即执行异步操作
  • 在返回值中添加 error 属性,用于存储异步操作的错误信息
  • 添加 onSuccessonError 参数,用于在异步操作成功和失败时执行额外的操作
  • 在调用 execute 函数时,无法传递参数,我们可以将 execute 函数改为接受一个参数,该参数为 fn 函数的参数

当我们改进后,useAsyncState 的结构如下:

import { ref, Ref } from 'vue'

interface UseAsyncStateReturnType<T, P extends any[]> {
  state: Ref<T>
  loading: Ref<boolean>
  execute: (...args: P) => void
  error: Ref<unknown>
}

interface UseAsyncStateOptions<T> {
  immediate?: boolean
  onSuccess?: (data: T) => void
  onError?: (e: unknown) => void
}

export function useAsyncState<T, P extends any[]>(
  fn: (...args: P) => Promise<T>,
  initialValue: T,
  options: UseAsyncStateOptions<T> = {}
): UseAsyncStateReturnType<T, P> {
  // 解构 options 参数
  const { immediate = false, onError, onSuccess } = options

  // 函数执行结果,默认为初始值
  const state = ref<T>(initialValue) as Ref<T>
  // loading 状态,默认为 false
  const loading = ref<boolean>(false)

  const error = ref<unknown>(null)

  async function execute(...args: any[]) {
    // 在执行异步动作之前将 error 设置为 null
    error.value = null
    // 将 loading 状态设置为 true
    loading.value = true
    try {
      const data = await fn(...(args as P))
      onSuccess?.(data)
    } catch (e: unknown) {
      error.value = error
      onError?.(e)
    } finally {
      // 将 loading 状态设置为 false
      loading.value = false
    }
  }

  if (immediate) {
    // 如果 immediate 为 true,则立即执行异步操作
    execute()
  }

  return {
    state,
    loading,
    execute,
    error
  }
}

在上面的代码中,我们添加了 error 属性,用于存储异步操作的错误信息,当异步操作成功时,我们会将 error 设置为 null,当异步操作失败时,我们会将 error 设置为错误信息。

性能优化

在上述代码中,我们将 state 定义为 Ref 类型,但是 Ref 是一个深度的响应式对象,在大部分情况下,我们使用 useAsyncState 获取到的数据只是用来做展示,所以我们应该避免使用 Ref,而是使用 ShallowRef 来代替,ShallowRef 它只会在修改ref.value时才会触发更新,而不会在修改ref.value的属性时触发更新。

import { ref, Ref, shallowRef } from 'vue'

interface UseAsyncStateReturnType<T, P extends any[]> {
  state: Ref<T>
  loading: Ref<boolean>
  execute: (...args: P) => void
  error: Ref<unknown>
}

interface UseAsyncStateOptions<T> {
  immediate?: boolean
  onSuccess?: (data: T) => void
  onError?: (e: unknown) => void
  shallow?: boolean
}

export function useAsyncState<T, P extends any[]>(
  fn: (...args: P) => Promise<T>,
  initialValue: T,
  options: UseAsyncStateOptions<T> = {}
): UseAsyncStateReturnType<T, P> {
  // 解构 options 参数
  const { immediate = false, shallow = true, onError, onSuccess } = options

  // 函数执行结果,默认为初始值
  const state = (shallow ? ref : shallowRef)<T>(initialValue) as Ref<T>
  // loading 状态,默认为 false
  const loading = ref<boolean>(false)

  const error = shallowRef<unknown>(null)

  async function execute(...args: any[]) {
    // 在执行异步动作之前将 error 设置为 null
    error.value = null
    // 将 loading 状态设置为 true
    loading.value = true
    try {
      const data = await fn(...(args as P))
      onSuccess?.(data)
    } catch (e: unknown) {
      error.value = error
      onError?.(e)
    } finally {
      // 将 loading 状态设置为 false
      loading.value = false
    }
  }

  if (immediate) {
    // 如果 immediate 为 true,则立即执行异步操作
    execute()
  }

  return {
    state,
    loading,
    execute,
    error
  }
}

至此,我们的 useAsyncState 就已经完成了。

使用

我们可以在组件中去使用 useAsyncState,来获取异步数据

<template>
  <div>
    <div v-if="loading">Loading...</div>
    <div v-else-if="state">Data: {{ state }}</div>
    <div v-else>Error</div>
    <button @click="execute">Execute</button>
  </div>
</template>

<script lang="ts" setup>
  import { useAsyncState } from './useAsyncState'

  const { state, loading, execute } = useAsyncState(
    () => new Promise(resolve => setTimeout(() => resolve('data'), 1000)),
    '',
    {
      immediate: true,
      onSuccess: data => console.log(data),
      onError: error => console.log(error)
    }
  )
</script>

我们来看一下它的效果

总结

在本篇文章中,我们实现了一个 useAsyncState,它可以帮助我们更加方便的获取异步数据,同时也可以帮助我们处理异步操作的错误,以及在异步操作成功和失败时执行额外的操作。

以上就是一文详解VueUse中useAsyncState的实现原理的详细内容,更多关于VueUse useAsyncState实现原理的资料请关注脚本之家其它相关文章!

相关文章

  • vue中集成省市区街四级地址组件的实现过程

    vue中集成省市区街四级地址组件的实现过程

    我们在开发中常会遇到选择地址的需求,有时候只需要选择省就可以,有时候则需要选择到市、县,以至于乡镇,甚至哪个村都有可能,下面这篇文章主要给大家介绍了关于vue中集成省市区街四级地址组件的相关资料,需要的朋友可以参考下
    2022-12-12
  • Vue.js 模板语法和数据绑定

    Vue.js 模板语法和数据绑定

    这篇文章主要介绍了Vue.js 模板语法和数据绑定,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下
    2022-05-05
  • Vue绑定用户接口实现代码示例

    Vue绑定用户接口实现代码示例

    这篇文章主要介绍了Vue绑定用户接口代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-11-11
  • element ui提交表单返回成功后自动清空表单的值的实现代码

    element ui提交表单返回成功后自动清空表单的值的实现代码

    这篇文章主要介绍了elementui提交表单返回成功后自动清空表单的值,本文通过两种方法结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-08-08
  • uni-app中使用ECharts配置四种不同的图表(示例详解)

    uni-app中使用ECharts配置四种不同的图表(示例详解)

    在uni-app中集成ECharts可以为我们的应用提供强大的图表功能,我们详细说一下如何在uni-app中使用ECharts,并配置四种不同的图表,感兴趣的朋友跟随小编一起看看吧
    2024-01-01
  • 1分钟Vue实现右键菜单

    1分钟Vue实现右键菜单

    今天给大家分享的是,如何在最短的时候内实现右键菜单。高效实现需求,避免重复造轮子。感兴趣的可以了解一下
    2021-10-10
  • Vue实现push数组并删除的例子

    Vue实现push数组并删除的例子

    今天小编就为大家分享一篇Vue实现push数组并删除的例子,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-11-11
  • vue3中实现异步组件的方法实例

    vue3中实现异步组件的方法实例

    前端开发经常遇到异步的问题,请求函数、链接库等,下面这篇文章主要给大家介绍了关于vue3中实现异步组件的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-06-06
  • vue使用Vue.extend方法仿写个loading加载中效果实例

    vue使用Vue.extend方法仿写个loading加载中效果实例

    在vue中提供v-loading命令,用于div的loading加载,下面这篇文章主要给大家介绍了关于vue使用Vue.extend方法仿写个loading加载中效果的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-06-06
  • vue+swiper实现左右滑动的测试题功能

    vue+swiper实现左右滑动的测试题功能

    这篇文章主要介绍了vue+swiper实现左右滑动的测试题功能,本文通过实例代码给大介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-10-10

最新评论