写在前面
一般我们在vue
项目中发送请求, 大多使用的是axios
, axios
有很多的优势,比如他的拦截器
,取消请求
,上传进度
, 而且是一个基于 promise
的网络请求库,可以用于浏览器和 node.js
等等…( 要不然也不会有这么多项目都在用 ) 我一直用的也是axios
不过 前几日看了vueuse
, 还有这种写法(⊙o⊙)? 有点意思啊,分享一下子📝
axios 与 useFetch
老样子,先看看 axios
与 useFetch
的区别
axios 🚚
基础使用 之 发送 get 请求
const axios = require('axios'); // 向给定ID的用户发起请求
axios.get('/user?ID=12345')
.then(function (response) {
// 处理成功情况 console.log(response); })
.catch(function (error) {
// 处理错误情况
console.log(error);
})
拦截器
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config;
},
function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
axios.interceptors.response.use(function (response) {
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
return response;
}, function (error) {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
return Promise.reject(error);
});
今天介绍一种新型的发送请求的方式(useFecth)
useFetch 🚀
基础使用 之 发送 get 请求
const { data } = useFetch(url).get().json()
手动执行
const { execute } = useFetch(url, { immediate: false })
execute()
拦截器
const { data } = useFetch(url, {
// 请求拦截器
async beforeFetch({ url, options, cancel }) {
// 在发送请求之前做些什么
options.token = 'xxx'
return {
options,
}
},
})
// 响应拦截器
const { data } = useFetch(url, {
afterFetch(ctx) {
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
return ctx
},
})
发布订阅获取数据
const { onFetchResponse, onFetchError } = useFetch(url)
onFetchResponse((response) => {
console.log(response.status)
})
onFetchError((error) => {
console.error(error.message)
})
是不是有点意思😊? 乍一看,有点难啊,其实很简单,听我细细道来 🗣️
原理简单分析
看见复杂方法不要慌,慢慢来,把 它拆开来,一个一个的抽丝剥茧
首先看入参
useFetch
接受两个参数,一个是url(string)
类型,一个是可选的options(Partial<object>)
其次看返回值
useFetch
返回的是一个对象,对象里面可以解构出data, onFetchResponse, onFetchError....
等等链式调用
useFetch
后面可以跟get
方法,get
方法可以跟json
方法,那么这些也是属于useFetch
的返回值里面的
那么聪明的你😊,一定可以写出类似这样的基础方法,对吗?
function useFetch(url,option = {}){
const defaultConfig = {
method:'get',
type = "json"
}
let config = {
...defaultConfig,
...option
};
let data ={name:'zs'};
let onFetchResponse =()=>{};
let onFetchError =()=>{};
let get =()=>{
config.method = "get"
return {
...shell
}
};
let json = ()=>{
config.type = "json"
return {
json:"json"
}
};
const shell= {
data,
onFetchResponse,
onFetchError,
get,
json
}
return {
...shell
}
}
let {data} = useFetch("xxx")
console.log(data) // {name:"zs"}
console.log(useFetch("xxx").get().json()) // {json:"json"}
好,基础结构搭好了,就向里面不断的追加功能就可以了
万事开头难,剩下的也不容易 🚗
🚗 useFetch 流程图
具体方法 🎈
其实原理已经很清晰了,剩下的就是写代码了💻
1. 合并 用户传递的config 与 内部默认的config
function useFetch (url: string,args:Partial<InternalConfig & UseFetchOptions>){
let options: UseFetchOptions = {
immediate: true,
refetch: false,
timeout: 0,
};
if (args) {
options = { ...options, ...args };
}
}
🔥 创建 execuate 方法
execuate
是真正的执行请求
的函数,是核心方法
function useFetch (url: string,args:Partial<InternalConfig & UseFetchOptions>){
// 基础配置项 🌳
const config: InternalConfig = {
method: "GET",
type: "text",
payload: undefined as unknown,
};
let fetchOptions: RequestInit = {};
const defaultFetchOptions: RequestInit = {
method: config.method,
headers: {},
};
// 中断请求 🎇
let controller: AbortController | undefined;
const abort = () => {
controller?.abort();
controller = new AbortController();
controller.signal.onabort = () => (aborted.value = true);
fetchOptions = {
...fetchOptions,
signal: controller.signal,
};
};
// 核心🔥🔥🔥🔥
let execute = async ()=>{
const context: BeforeFetchContext = {
url: url,
options: {
...defaultFetchOptions,
...fetchOptions,
},
cancel: () => {
isCanceled = true;
},
};
}
// 🔥🔥🔥🔥
// 把用户的 请求拦截器的返回值 与当前的 context 合并,改变原有的context
if (options.beforeFetch) {
Object.assign(context, await options.beforeFetch(context));
}
return new Promise<Response | null>((resolve, reject) => {
fetch.....
}
}
ok,execuate
就是把用户的自定义的config
和 内部的config
进行合并为context
,
如果用户传递了请求拦截器(beforeFetch)
可以再次对context
做出自定义
🔥 fetch 请求数据
let execute = async(){
let responseData: any = null;
// 最后要抛出的给用户的数据
let data = ref();
return new Promise<Response | null>((resolve, reject) =>{
fetch(context.url, {
...defaultFetchOptions,
...context.options,
headers: {
...headersToObject(defaultFetchOptions.headers),
...headersToObject(context.options?.headers),
},
}).then(async fetchResponse=>{
// 保留初始数据
response.value = fetchResponse;
// fetchResponse 不能直接使用,需要 使用 json / text 转化格式
responseData = await fetchResponse.json();
// 如果有响应拦截器 🔥🔥🔥🔥
if (options.afterFetch) {
// 返回 数据解构 data 赋值给 responseData
({ data: responseData } = await options.afterFetch({
data: responseData,
response: fetchResponse,
}));
}
data.value = responseData;
// 发布订阅模式中的 发布 🔥🔥🔥🔥
responseEvent.trigger(fetchResponse);
return resolve(fetchResponse);
}).catch(err=>{
/// 和 then 同理
})
}
fetch
不难的,其实就是发送请求返回一个Promise
请求成功之后触发 responseEvent.trigger(fetchResponse)
🐵 responseEvent.trigger(发布订阅)
发布订阅 模式
是前端常见的设计模式,原理就是事件触发on
的时候,把要执行的方法
push
进一个数组里,等到trigger
的时候依次触发数组中的函数,很简单但很实用
function createEventHook<T = any>(): EventHook<T> {
const fns: Set<(param: T) => void> = new Set();
const off = (fn: (param: T) => void) => {
fns.delete(fn);
};
const on = (fn: (param: T) => void) => {
fns.add(fn);
const offFn = () => off(fn);
return {
off: offFn,
};
};
const trigger = (param: T) => {
return Promise.all(Array.from(fns).map(fn => fn(param)));
};
return {
on,
off,
trigger,
};
}
在 fetch 请求成功之后触发
// ....
fetch().then(async fetchResponse=>{
responseEvent.trigger(fetchResponse);
})
//....
🎉最后一步 — 抛出数据
const useFetch = (url: string,args:Partial<InternalConfig & UseFetchOptions>) => {
// ....
return {
data,
execute,
abort,
// 抛出 函数 onFetchResponse
onFetchResponse: responseEvent.on,
// 设置 请求方法
get: setMethod("GET"),
// 设置fetch 成功之后用什么格式解析
json: setType("json"),
}
}
👨用户使用
再回头看开始,是不是就更加的明了了
用户获取 数据 的 时候
就是简单的定义了一个全局的响应式变量(data)
,当请求成功之后赋值,最后抛出const { data } = useFetch(url)
拦截器
请求拦截器 就是对context
做进一步的处理,最后fetch
拿这个最新的context
请求
响应拦截器 返回后的结果进行处理,然后赋值给全局的响应式变量(data)
const { data } = useFetch(url, {
async beforeFetch({ url, options, cancel }) {
const myToken = await getMyToken()
if (!myToken){
cancel()
}
options.headers = {
...options.headers,
Authorization: `Bearer ${myToken}`,
}
return {
options,
}
},
afterFetch(ctx) {
if (ctx.data.title === 'HxH'){
ctx.data.title = 'Hunter x Hunter' // Modifies the response data return ctx },
}
}
})
- 发布订阅
用户使用的onFetchResponse
在内部使用的是responseEvent.on
,也就是触发订阅操作
当事件触发之后,自然会自动执行
const { onFetchResponse, onFetchError } = useFetch(url)
onFetchResponse((response) => {
console.log(response.status)
})
onFetchError((error) => {
console.error(error.message)
})
🥟 总结
又是一个有趣的hook
,从这个hook
中可以学到这种发布订阅
的思路,也可以学到单独的数据拦截
这种思路
vueuse 是一个很好的 学习vue3,甚至可以说是学习 开发思路 的一个很棒的库