一文詳解VueUse中useAsyncState的實現(xiàn)原理
useAsyncState
是一個用于管理異步狀態(tài)的自定義鉤子函數(shù)。它是你簡化異步操作的最佳拍檔,就像魚兒離不開水,雄鷹離不開天空,你老婆離不開你,同時異步也離不開 useAsyncState
,它簡化了在Vue組件中處理異步操作的過程,如發(fā)送網(wǎng)絡請求、加載數(shù)據(jù)或執(zhí)行其他耗時的任務。
背景
在Vue 3 Composition API中,我們可以使用自定義鉤子函數(shù)來封裝可復用的邏輯。useAsyncState是一個強大而靈活的自定義鉤子函數(shù),幫助我們管理異步操作的狀態(tài),使代碼更簡潔、可讀性更強。
我們先來看一下我們平時在項目開發(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>
在上面的代碼中,我們可以看到,我們需要定義一個data
變量來存儲異步操作的結果,還需要定義一個loading
變量來存儲異步操作的加載狀態(tài),還需要定義一個loadData
函數(shù)來執(zhí)行異步操作,當異步操作開始時,我們需要將loading
設置為true
,當異步操作結束時,我們需要將loading
設置為false
,當異步操作成功時,我們需要將結果賦值給data
,在這段代碼中,我們需要定義兩個變量和一個函數(shù),來處理異步操作,這樣的代碼顯然不夠優(yōu)雅,我們可以通過自定義鉤子函數(shù)來優(yōu)化它。
針對上面的代碼,我們不難發(fā)現(xiàn),當我們需要獲取異步狀態(tài)時,需要一個變量來接受獲取到的異步狀態(tài),有時我們也會需要用到loading屬性來判斷異步操作是否正在加載,為此我們需要完成 useAsyncState
來實現(xiàn)它的功能,我們下來看一下實現(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>
對此我們使用短短十行代碼,就實現(xiàn)了異步操作的狀態(tài)管理,比起之前的代碼,我們可以看到,我們不需要定義額外的變量和函數(shù),只需要調用useAsyncState
函數(shù),就可以獲取異步操作的狀態(tài),這樣的代碼更加簡潔,可讀性更強。
目的
useAsyncState
旨在提供以下功能:
- 方便管理異步操作的狀態(tài)
- 處理異步操作的加載中、成功和錯誤等不同狀態(tài)
- 支持自定義操作,如在成功或失敗時執(zhí)行其他邏輯
- 提供性能優(yōu)化選項,避免不必要的更新
基礎設計
在實現(xiàn)useAsyncState
之前,我們先來初步的設計一下它的結構:
參數(shù)
名稱 | 描述 | 類型 | 必傳 | 默認值 |
---|---|---|---|---|
fn | 用于執(zhí)行異步操作的函數(shù),該函數(shù)返回一個 Promise ,在調用返回值 execute 時將會執(zhí)行該函數(shù),將 Promise 結果賦值給 state | (...args) => Promise<any> | 是 | - |
initialValue | 默認值,fn函數(shù)未執(zhí)行完成之前,state將為默認值,該參數(shù)的類型應和 fn 參數(shù)返回的 Promise 結果類型相同 | Awaited<ReturnType> | 是 | - |
返回值
名稱 | 描述 | 類型 |
---|---|---|
state | 異步操作的狀態(tài),初始值為"initialValue",當 fn 返回的Promise狀態(tài)完成時,將結果賦值給 state | Awaited<ReturnType> |
loading | 異步操作是否正在加載中,初始值為false ,當 fn 執(zhí)行時,狀態(tài)為loadin為true,Promise狀態(tài)完成時,loading為false | boolean |
execute | 執(zhí)行異步操作的函數(shù),調用時會執(zhí)行 fn 函數(shù),重新獲取異步狀態(tài) | () => void |
注:我們最初的設計是為了方便大家的理解,只實現(xiàn)基本的功能,后續(xù)我們會對其進行改進,使其更加易用。
實現(xiàn)
有了上面的設計,我們就可以開始實現(xiàn)useAsyncState
了。首先,我們需要定義一個useAsyncState
函數(shù),該函數(shù)接受兩個參數(shù),分別是fn
和initialValue
,并返回一個對象,該對象包含多個屬性。
import { ref, Ref } from 'vue' interface UseAsyncStateReturnType<T> { state: Ref<T> loading: Ref<boolean> execute: () => void } /** * 響應式異步狀態(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,默認為初始值 const state = ref<T>(initialValue) // loading 狀態(tài),默認為 false const loading = ref<boolean>(false) async function execute() { // 將 loading 狀態(tài)設置為 true loading.value = true try { const data = await fn() } finally { // 將 loading 狀態(tài)設置為 false loading.value = false } } return { state, loading, execute } }
在上面的代碼中,我們實現(xiàn)了 useAsyncState
的基本功能,它接受兩個參數(shù),分別是 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>
在使用過程中,我們發(fā)現(xiàn)useAsyncState
還有一些不足之處:
- 需要手動調用
execute
函數(shù),才能執(zhí)行異步操作 - 調用
execute
函數(shù)時,無法傳遞參數(shù) - 無法處理異步操作的錯誤
- 如果我們需要再響應成功和失敗的情況下,執(zhí)行不同的操作,就需要在
execute
函數(shù)中添加額外的邏輯
為了解決上面的問題,我們可以對useAsyncState
進行改進,使其更加易用。
改進
功能改進
我們針對上面的問題,梳理一下我們的解決方案:
- 添加
immediate
參數(shù),用于控制是否立即執(zhí)行異步操作 - 在返回值中添加
error
屬性,用于存儲異步操作的錯誤信息 - 添加
onSuccess
和onError
參數(shù),用于在異步操作成功和失敗時執(zhí)行額外的操作 - 在調用
execute
函數(shù)時,無法傳遞參數(shù),我們可以將execute
函數(shù)改為接受一個參數(shù),該參數(shù)為fn
函數(shù)的參數(shù)
當我們改進后,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 參數(shù) const { immediate = false, onError, onSuccess } = options // 函數(shù)執(zhí)行結果,默認為初始值 const state = ref<T>(initialValue) as Ref<T> // loading 狀態(tài),默認為 false const loading = ref<boolean>(false) const error = ref<unknown>(null) async function execute(...args: any[]) { // 在執(zhí)行異步動作之前將 error 設置為 null error.value = null // 將 loading 狀態(tài)設置為 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)設置為 false loading.value = false } } if (immediate) { // 如果 immediate 為 true,則立即執(zhí)行異步操作 execute() } return { state, loading, execute, error } }
在上面的代碼中,我們添加了 error
屬性,用于存儲異步操作的錯誤信息,當異步操作成功時,我們會將 error
設置為 null
,當異步操作失敗時,我們會將 error
設置為錯誤信息。
性能優(yōu)化
在上述代碼中,我們將 state
定義為 Ref
類型,但是 Ref
是一個深度的響應式對象,在大部分情況下,我們使用 useAsyncState
獲取到的數(shù)據(jù)只是用來做展示,所以我們應該避免使用 Ref
,而是使用 ShallowRef
來代替,ShallowRef
它只會在修改ref.value
時才會觸發(fā)更新,而不會在修改ref.value
的屬性時觸發(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> { // 解構 options 參數(shù) const { immediate = false, shallow = true, onError, onSuccess } = options // 函數(shù)執(zhí)行結果,默認為初始值 const state = (shallow ? ref : shallowRef)<T>(initialValue) as Ref<T> // loading 狀態(tài),默認為 false const loading = ref<boolean>(false) const error = shallowRef<unknown>(null) async function execute(...args: any[]) { // 在執(zhí)行異步動作之前將 error 設置為 null error.value = null // 將 loading 狀態(tài)設置為 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)設置為 false loading.value = false } } if (immediate) { // 如果 immediate 為 true,則立即執(zhí)行異步操作 execute() } return { state, loading, execute, error } }
至此,我們的 useAsyncState
就已經完成了。
使用
我們可以在組件中去使用 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>
我們來看一下它的效果
總結
在本篇文章中,我們實現(xiàn)了一個 useAsyncState
,它可以幫助我們更加方便的獲取異步數(shù)據(jù),同時也可以幫助我們處理異步操作的錯誤,以及在異步操作成功和失敗時執(zhí)行額外的操作。
以上就是一文詳解VueUse中useAsyncState的實現(xiàn)原理的詳細內容,更多關于VueUse useAsyncState實現(xiàn)原理的資料請關注腳本之家其它相關文章!
相關文章
教你使用vue-autofit 一行代碼搞定自適應可視化大屏
這篇文章主要為大家介紹了使用vue-autofit 一行代碼搞定自適應可視化大屏教程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-05-05Vue中"This dependency was not found"問題的解決方法
這篇文章主要介紹了Vue中"This dependency was not found"的問題的解決方法,需要的朋友可以參考下2018-06-06Element的穿梭框數(shù)據(jù)量大時點擊全選卡頓的解決方案
本文主要介紹了Element的穿梭框數(shù)據(jù)量大時點擊全選卡頓的解決方案,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-10-10