一文详解VueUse中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状态完成时,将结果赋值给 state | Awaited<ReturnType> |
loading | 异步操作是否正在加载中,初始值为false ,当 fn 执行时,状态为loadin为true,Promise状态完成时,loading为false | boolean |
execute | 执行异步操作的函数,调用时会执行 fn 函数,重新获取异步状态 | () => void |
注:我们最初的设计是为了方便大家的理解,只实现基本的功能,后续我们会对其进行改进,使其更加易用。
实现
有了上面的设计,我们就可以开始实现useAsyncState
了。首先,我们需要定义一个useAsyncState
函数,该函数接受两个参数,分别是fn
和initialValue
,并返回一个对象,该对象包含多个属性。
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
属性,用于存储异步操作的错误信息 - 添加
onSuccess
和onError
参数,用于在异步操作成功和失败时执行额外的操作 - 在调用
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.js基础之监听子组件事件v-on及绑定数据v-model学习
这篇文章主要为大家介绍了Vue.js基础之监听子组件事件v-on及绑定数据v-model学习,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪2023-06-06
最新评论