一文詳解VueUse中useAsyncState的實(shí)現(xiàn)原理
useAsyncState是一個(gè)用于管理異步狀態(tài)的自定義鉤子函數(shù)。它是你簡(jiǎn)化異步操作的最佳拍檔,就像魚兒離不開水,雄鷹離不開天空,你老婆離不開你,同時(shí)異步也離不開 useAsyncState,它簡(jiǎn)化了在Vue組件中處理異步操作的過程,如發(fā)送網(wǎng)絡(luò)請(qǐng)求、加載數(shù)據(jù)或執(zhí)行其他耗時(shí)的任務(wù)。
背景
在Vue 3 Composition API中,我們可以使用自定義鉤子函數(shù)來封裝可復(fù)用的邏輯。useAsyncState是一個(gè)強(qiáng)大而靈活的自定義鉤子函數(shù),幫助我們管理異步操作的狀態(tài),使代碼更簡(jiǎn)潔、可讀性更強(qiáng)。
我們先來看一下我們平時(shí)在項(xiàng)目開發(fā)的過程中如何使用異步狀態(tài):
<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>
在上面的代碼中,我們可以看到,我們需要定義一個(gè)data變量來存儲(chǔ)異步操作的結(jié)果,還需要定義一個(gè)loading變量來存儲(chǔ)異步操作的加載狀態(tài),還需要定義一個(gè)loadData函數(shù)來執(zhí)行異步操作,當(dāng)異步操作開始時(shí),我們需要將loading設(shè)置為true,當(dāng)異步操作結(jié)束時(shí),我們需要將loading設(shè)置為false,當(dāng)異步操作成功時(shí),我們需要將結(jié)果賦值給data,在這段代碼中,我們需要定義兩個(gè)變量和一個(gè)函數(shù),來處理異步操作,這樣的代碼顯然不夠優(yōu)雅,我們可以通過自定義鉤子函數(shù)來優(yōu)化它。
針對(duì)上面的代碼,我們不難發(fā)現(xiàn),當(dāng)我們需要獲取異步狀態(tài)時(shí),需要一個(gè)變量來接受獲取到的異步狀態(tài),有時(shí)我們也會(huì)需要用到loading屬性來判斷異步操作是否正在加載,為此我們需要完成 useAsyncState 來實(shí)現(xiàn)它的功能,我們下來看一下實(shí)現(xiàn)后的代碼:
<template>
<div>{{ data }}</div>
</template>
<script lang="ts" setup>
import { useAsyncState } from './useAsyncState'
import { getData } from './api'
const { data, loading } = useAsyncState(getData, null) // 傳遞異步操作的函數(shù)和初始值
</script>
對(duì)此我們使用短短十行代碼,就實(shí)現(xiàn)了異步操作的狀態(tài)管理,比起之前的代碼,我們可以看到,我們不需要定義額外的變量和函數(shù),只需要調(diào)用useAsyncState函數(shù),就可以獲取異步操作的狀態(tài),這樣的代碼更加簡(jiǎn)潔,可讀性更強(qiáng)。
目的
useAsyncState 旨在提供以下功能:
- 方便管理異步操作的狀態(tài)
- 處理異步操作的加載中、成功和錯(cuò)誤等不同狀態(tài)
- 支持自定義操作,如在成功或失敗時(shí)執(zhí)行其他邏輯
- 提供性能優(yōu)化選項(xiàng),避免不必要的更新
基礎(chǔ)設(shè)計(jì)
在實(shí)現(xiàn)useAsyncState之前,我們先來初步的設(shè)計(jì)一下它的結(jié)構(gòu):
參數(shù)
| 名稱 | 描述 | 類型 | 必傳 | 默認(rèn)值 |
|---|---|---|---|---|
| fn | 用于執(zhí)行異步操作的函數(shù),該函數(shù)返回一個(gè) Promise,在調(diào)用返回值 execute 時(shí)將會(huì)執(zhí)行該函數(shù),將 Promise 結(jié)果賦值給 state | (...args) => Promise<any> | 是 | - |
| initialValue | 默認(rèn)值,fn函數(shù)未執(zhí)行完成之前,state將為默認(rèn)值,該參數(shù)的類型應(yīng)和 fn 參數(shù)返回的 Promise 結(jié)果類型相同 | Awaited<ReturnType> | 是 | - |
返回值
| 名稱 | 描述 | 類型 |
|---|---|---|
| state | 異步操作的狀態(tài),初始值為"initialValue",當(dāng) fn 返回的Promise狀態(tài)完成時(shí),將結(jié)果賦值給 state | Awaited<ReturnType> |
| loading | 異步操作是否正在加載中,初始值為false,當(dāng) fn 執(zhí)行時(shí),狀態(tài)為loadin為true,Promise狀態(tài)完成時(shí),loading為false | boolean |
| execute | 執(zhí)行異步操作的函數(shù),調(diào)用時(shí)會(huì)執(zhí)行 fn 函數(shù),重新獲取異步狀態(tài) | () => void |
注:我們最初的設(shè)計(jì)是為了方便大家的理解,只實(shí)現(xiàn)基本的功能,后續(xù)我們會(huì)對(duì)其進(jìn)行改進(jìn),使其更加易用。
實(shí)現(xiàn)
有了上面的設(shè)計(jì),我們就可以開始實(shí)現(xiàn)useAsyncState了。首先,我們需要定義一個(gè)useAsyncState函數(shù),該函數(shù)接受兩個(gè)參數(shù),分別是fn和initialValue,并返回一個(gè)對(duì)象,該對(duì)象包含多個(gè)屬性。
import { ref, Ref } from 'vue'
interface UseAsyncStateReturnType<T> {
state: Ref<T>
loading: Ref<boolean>
execute: () => void
}
/**
* 響應(yīng)式異步狀態(tài)管理
* @param {() => Promise<T>} fn
* @param {T} initialValue
* @returns {UseAsyncStateReturnType<T>}
*/
export function useAsyncState<T>(
fn: () => Promise<T>,
initialValue: T
): UseAsyncStateReturnType<T> {
// 用于保存異步狀態(tài)的 ref,默認(rèn)為初始值
const state = ref<T>(initialValue)
// loading 狀態(tài),默認(rèn)為 false
const loading = ref<boolean>(false)
async function execute() {
// 將 loading 狀態(tài)設(shè)置為 true
loading.value = true
try {
const data = await fn()
} finally {
// 將 loading 狀態(tài)設(shè)置為 false
loading.value = false
}
}
return {
state,
loading,
execute
}
}
在上面的代碼中,我們實(shí)現(xiàn)了 useAsyncState 的基本功能,它接受兩個(gè)參數(shù),分別是 fn 和 initialValue,并返回一個(gè)對(duì)象,該對(duì)象包含我們約定的屬性。
此時(shí)我們的 useAsyncState 就已經(jīng)完成了,我們可以在組件中去使用它
<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>
在使用過程中,我們發(fā)現(xiàn)useAsyncState還有一些不足之處:
- 需要手動(dòng)調(diào)用
execute函數(shù),才能執(zhí)行異步操作 - 調(diào)用
execute函數(shù)時(shí),無法傳遞參數(shù) - 無法處理異步操作的錯(cuò)誤
- 如果我們需要再響應(yīng)成功和失敗的情況下,執(zhí)行不同的操作,就需要在
execute函數(shù)中添加額外的邏輯
為了解決上面的問題,我們可以對(duì)useAsyncState進(jìn)行改進(jìn),使其更加易用。
改進(jìn)
功能改進(jìn)
我們針對(duì)上面的問題,梳理一下我們的解決方案:
- 添加
immediate參數(shù),用于控制是否立即執(zhí)行異步操作 - 在返回值中添加
error屬性,用于存儲(chǔ)異步操作的錯(cuò)誤信息 - 添加
onSuccess和onError參數(shù),用于在異步操作成功和失敗時(shí)執(zhí)行額外的操作 - 在調(diào)用
execute函數(shù)時(shí),無法傳遞參數(shù),我們可以將execute函數(shù)改為接受一個(gè)參數(shù),該參數(shù)為fn函數(shù)的參數(shù)
當(dāng)我們改進(jìn)后,useAsyncState 的結(jié)構(gòu)如下:
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> {
// 解構(gòu) options 參數(shù)
const { immediate = false, onError, onSuccess } = options
// 函數(shù)執(zhí)行結(jié)果,默認(rèn)為初始值
const state = ref<T>(initialValue) as Ref<T>
// loading 狀態(tài),默認(rèn)為 false
const loading = ref<boolean>(false)
const error = ref<unknown>(null)
async function execute(...args: any[]) {
// 在執(zhí)行異步動(dòng)作之前將 error 設(shè)置為 null
error.value = null
// 將 loading 狀態(tài)設(shè)置為 true
loading.value = true
try {
const data = await fn(...(args as P))
onSuccess?.(data)
} catch (e: unknown) {
error.value = error
onError?.(e)
} finally {
// 將 loading 狀態(tài)設(shè)置為 false
loading.value = false
}
}
if (immediate) {
// 如果 immediate 為 true,則立即執(zhí)行異步操作
execute()
}
return {
state,
loading,
execute,
error
}
}
在上面的代碼中,我們添加了 error 屬性,用于存儲(chǔ)異步操作的錯(cuò)誤信息,當(dāng)異步操作成功時(shí),我們會(huì)將 error 設(shè)置為 null,當(dāng)異步操作失敗時(shí),我們會(huì)將 error 設(shè)置為錯(cuò)誤信息。
性能優(yōu)化
在上述代碼中,我們將 state 定義為 Ref 類型,但是 Ref 是一個(gè)深度的響應(yīng)式對(duì)象,在大部分情況下,我們使用 useAsyncState 獲取到的數(shù)據(jù)只是用來做展示,所以我們應(yīng)該避免使用 Ref,而是使用 ShallowRef 來代替,ShallowRef 它只會(huì)在修改ref.value時(shí)才會(huì)觸發(fā)更新,而不會(huì)在修改ref.value的屬性時(shí)觸發(fā)更新。
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> {
// 解構(gòu) options 參數(shù)
const { immediate = false, shallow = true, onError, onSuccess } = options
// 函數(shù)執(zhí)行結(jié)果,默認(rèn)為初始值
const state = (shallow ? ref : shallowRef)<T>(initialValue) as Ref<T>
// loading 狀態(tài),默認(rèn)為 false
const loading = ref<boolean>(false)
const error = shallowRef<unknown>(null)
async function execute(...args: any[]) {
// 在執(zhí)行異步動(dòng)作之前將 error 設(shè)置為 null
error.value = null
// 將 loading 狀態(tài)設(shè)置為 true
loading.value = true
try {
const data = await fn(...(args as P))
onSuccess?.(data)
} catch (e: unknown) {
error.value = error
onError?.(e)
} finally {
// 將 loading 狀態(tài)設(shè)置為 false
loading.value = false
}
}
if (immediate) {
// 如果 immediate 為 true,則立即執(zhí)行異步操作
execute()
}
return {
state,
loading,
execute,
error
}
}
至此,我們的 useAsyncState 就已經(jīng)完成了。
使用
我們可以在組件中去使用 useAsyncState,來獲取異步數(shù)據(jù)
<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>
我們來看一下它的效果

總結(jié)
在本篇文章中,我們實(shí)現(xiàn)了一個(gè) useAsyncState,它可以幫助我們更加方便的獲取異步數(shù)據(jù),同時(shí)也可以幫助我們處理異步操作的錯(cuò)誤,以及在異步操作成功和失敗時(shí)執(zhí)行額外的操作。
以上就是一文詳解VueUse中useAsyncState的實(shí)現(xiàn)原理的詳細(xì)內(nèi)容,更多關(guān)于VueUse useAsyncState實(shí)現(xiàn)原理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
教你使用vue-autofit 一行代碼搞定自適應(yīng)可視化大屏
這篇文章主要為大家介紹了使用vue-autofit 一行代碼搞定自適應(yīng)可視化大屏教程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05
Vue中"This dependency was not found"問題的解決方法
這篇文章主要介紹了Vue中"This dependency was not found"的問題的解決方法,需要的朋友可以參考下2018-06-06
Vue版本vue2.9.6升級(jí)到vue3.0的詳細(xì)步驟
vue版本升級(jí)相信大家應(yīng)該都遇到過,下面這篇文章主要給大家介紹了關(guān)于Vue版本vue2.9.6升級(jí)到vue3.0的詳細(xì)步驟,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2022-09-09
Element的穿梭框數(shù)據(jù)量大時(shí)點(diǎn)擊全選卡頓的解決方案
本文主要介紹了Element的穿梭框數(shù)據(jù)量大時(shí)點(diǎn)擊全選卡頓的解決方案,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-10-10
Vue項(xiàng)目接入Paypal實(shí)現(xiàn)示例詳解
這篇文章主要介紹了Vue項(xiàng)目接入Paypal實(shí)現(xiàn)示例詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06

