Vue3使用hook封裝常見的幾種異步請(qǐng)求函數(shù)場景,讓開發(fā)更絲滑
?? 立即請(qǐng)求函數(shù)
我們期望的場景是,
- 初始化一個(gè)請(qǐng)求函數(shù),然后根據(jù)初始參數(shù),立即發(fā)送請(qǐng)求
- 返回請(qǐng)求結(jié)果,并且返回請(qǐng)求回調(diào)函數(shù),方便我們根據(jù)新的參數(shù)再次調(diào)用
- 要求返回值包含加載中狀態(tài),錯(cuò)誤信息以及正常的數(shù)據(jù)類型。
我的實(shí)現(xiàn)過程如下:
定義具體的數(shù)據(jù)返回值簽名
interface Fetch<T> {
loading: boolean,
value?: T, // 具體的返回類型是泛型
error?: string
}
完整的簽名如下
export declare function useFetch <T, Params>(
fn: (args: Params) => Promise<T>,
initParams: Params
): [Fetch<T>, (params: Params) => Promise<unknown>]
函數(shù)實(shí)現(xiàn)如下:
export const useFetch = <T, Params>(
fn: (args: Params) => Promise<T>,
initParams: Params
): [Fetch<T>, (params: Params) => Promise<T>] => {
// 定義基礎(chǔ)的數(shù)據(jù)格式
const data = reactive<Fetch<T>>({
loading: true,
value: undefined,
error: undefined
}) as Fetch<T> // 這里會(huì)報(bào)錯(cuò):T類型無法賦值給UnwrapRef<T>類型
// 我不懂為啥,UnwrapRef是vue的深層響應(yīng)式類型的聲明
// 這里導(dǎo)致我無法默認(rèn)的類型賦值,不符合直覺,可能是我ts太菜了
// 懂的大佬評(píng)論區(qū)帶帶我吧
// 定義請(qǐng)求回調(diào)
const callback = (params: Params): Promise<T> => new Promise((resolve, reject) => {
data.loading = true
fn(params)
.then(result => {
data.value = result as any
resolve(result)
})
.catch(error => {
data.error = error
reject(error)
})
.finally(() => data.loading = false)
})
// 立即執(zhí)行
watchSyncEffect(() => {
callback(initParams)
})
return [data, callback]
}
我們驗(yàn)證下
<script setup lang="ts">
import { reactive } from 'vue';
import { useFetch } from './hooks/index';
const fn = () => new Promise((resolve) => {
setTimeout(()=> resolve({data: [], msg: '', code: 200}), 1000)
})
const [data, fetch] = useFetch<unknown, object>(fn, {})
</script>
<template>
<h4>公眾號(hào):萌萌噠草頭將軍</h4>
<!-- 加載中時(shí)用css禁用按鈕 -->
<button
:style="{'pointer-events': data.loading ? 'none' : 'auto'}"
@click="fetch({})"
>{{ data.loading ? 'laoding...' : 'fetch' }}</button>
<h1 v-if="data.loading">loading...</h1>
<h1 v-else>{{data.value}}</h1>
</template>
fetch.gif
頁面刷新后立即發(fā)出請(qǐng)求了!
?? 手動(dòng)請(qǐng)求函數(shù)
我們期望的場景是,
- 初始化一個(gè)請(qǐng)求函數(shù)
- 返回請(qǐng)求回調(diào)函數(shù),方便我們調(diào)用
- 要求返回值包含加載中狀態(tài),錯(cuò)誤信息以及正常的數(shù)據(jù)類型
這個(gè)的實(shí)現(xiàn)和上面類似,我們不需要初始參數(shù)和類型,也不用立即執(zhí)行,
完整的簽名如下
export declare function useFetch <T>(
fn: (args: unknown) => Promise<T>
): [Fetch<T>, (params: unknown) => Promise<T>]
實(shí)現(xiàn)如下:
export const useFetchFn = <T>(
fn: (args: unknown) => Promise<T>
): [Fetch<T>, (params: unknown) => Promise<T>] => {
const data = reactive<Fetch<T>>({
loading: false, // 默認(rèn)為false
value: undefined,
error: undefined
}) as Fetch<T>
const callback = (params: unknown): Promise<T> => new Promise((resolve, reject) => {
data.loading = true
fn(params)
.then(result => {
data.value = result as any
resolve(result)
})
.catch(error => {
data.error = error
reject(error)
})
.finally(() => data.loading = false)
})
return [data, callback]
}
驗(yàn)證如下:
<script setup lang="ts">
import { reactive } from 'vue';
import { useFetchFn } from './hooks/index';
const fn = () => new Promise((resolve) => {
setTimeout(()=> resolve({data: [], msg: '', code: 200}), 1000)
})
const [data, fetch] = useFetchFn<unknown>(fn)
</script>
<template>
<h4>公眾號(hào):萌萌噠草頭將軍</h4>
<!-- 加載中時(shí)用css禁用按鈕 -->
<button
:style="{'pointer-events': data.loading ? 'none' : 'auto'}"
@click="fetch({})"
>{{ data.loading ? 'laoding...' : 'fetch' }}</button>
<h1 v-if="data.loading">loading...</h1>
<h1 v-else>{{data.value}}</h1>
</template>
fetchfn.gif
頁面刷新后沒有發(fā)出請(qǐng)求,點(diǎn)擊按鈕之后才發(fā)出請(qǐng)求!
?? 自動(dòng)請(qǐng)求函數(shù)
我們期望的場景是,
- 初始化一個(gè)請(qǐng)求函數(shù),然后根據(jù)初始參數(shù),立即發(fā)送請(qǐng)求
- 當(dāng)參數(shù)發(fā)生變化時(shí),自動(dòng)根據(jù)最新參數(shù)發(fā)送請(qǐng)求
- 要求返回值包含加載中狀態(tài),錯(cuò)誤信息以及正常的數(shù)據(jù)類型。
這個(gè)的實(shí)現(xiàn)和立即請(qǐng)求函數(shù)類似,只需要監(jiān)聽參數(shù)的變化,
完整的簽名如下
export declare function useFetch <T, Params>(
fn: (args: Params) => Promise<T>,
initParams: Params
): Fetch<T>
實(shí)現(xiàn)如下:
export const useAutoFetch = <T, Params>(
fn: (args: Params) => Promise<T>,
initParams: Params
): Fetch<T> => {
const data = reactive<Fetch<T>>({
loading: true,
value: undefined,
error: undefined
}) as Fetch<T>
const callback = (params: Params): Promise<T> => new Promise((resolve, reject) => {
data.loading = true
fn(params)
.then(result => {
data.value = result as any
resolve(result)
})
.catch(error => {
data.error = error
reject(error)
})
.finally(() => data.loading = false)
})
// 如果不需要立即執(zhí)行,可沒有這步
const effects = watchSyncEffect(() => {
callback(initParams)
})
// 自動(dòng)監(jiān)聽參數(shù)變化
const effects = watch([initParams], () => callback(initParams))
// 卸載頁面時(shí),關(guān)閉監(jiān)聽
onUnmounted(() => effects())
return data
}
我們驗(yàn)證下功能
<script setup lang="ts">
import { reactive, watch } from 'vue';
import { useAutoFetch } from './hooks/index';
const fn = () => new Promise((resolve) => {
setTimeout(()=> resolve({data: [], msg: '', code: 200}), 1000)
})
const params = reactive({
age: 9
})
const data = useAutoFetch<unknown, object>(fn, params)
watch(params, () => console.log(params))
</script>
<template>
<h4>公眾號(hào):萌萌噠草頭將軍</h4>
<div>{{ params.age }}</div>
<!-- 加載中時(shí)用css禁用按鈕 -->
<button
:style="{'pointer-events': data.loading ? 'none' : 'auto'}"
@click="() => params.age++"
>{{ data.loading ? 'laoding...' : 'change params' }}</button>
<h1 v-if="data.loading">loading...</h1>
<h1 v-else>{{data.value}}</h1>
</template>
auto.gif
每次當(dāng)我們改變參數(shù)時(shí)自動(dòng)發(fā)送請(qǐng)求。