首页 实用干货 🌋

写在前面

一般我们在vue项目中发送请求, 大多使用的是axios, axios 有很多的优势,比如他的拦截器,取消请求,上传进度, 而且是一个基于 promise 的网络请求库,可以用于浏览器和 node.js等等…( 要不然也不会有这么多项目都在用 ) 我一直用的也是axios

不过 前几日看了vueuse, 还有这种写法(⊙o⊙)? 有点意思啊,分享一下子📝

axios 与 useFetch

老样子,先看看 axiosuseFetch 的区别

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)
})

是不是有点意思😊? 乍一看,有点难啊,其实很简单,听我细细道来 🗣️

原理简单分析

看见复杂方法不要慌,慢慢来,把 它拆开来,一个一个的抽丝剥茧

  1. 首先看入参
    useFetch 接受两个参数,一个是url(string)类型,一个是可选的options(Partial<object>)

  2. 其次看返回值
    useFetch 返回的是一个对象,对象里面可以解构出data, onFetchResponse, onFetchError....等等

  3. 链式调用
    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 流程图

image.png

具体方法 🎈

其实原理已经很清晰了,剩下的就是写代码了💻

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"),
    }
}

👨用户使用

再回头看开始,是不是就更加的明了了

  1. 用户获取 数据 的 时候
    就是简单的定义了一个全局的响应式变量(data),当请求成功之后赋值,最后抛出

    const { data } = useFetch(url)
    
  2. 拦截器
    请求拦截器 就是对 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 },
     }
}
})
  1. 发布订阅
    用户使用的 onFetchResponse 在内部使用的是responseEvent.on,也就是触发订阅操作
    事件触发之后,自然会自动执行
const { onFetchResponse, onFetchError } = useFetch(url)

onFetchResponse((response) => {
    console.log(response.status)
})

onFetchError((error) => {
    console.error(error.message)
})

🥟 总结

又是一个有趣的hook,从这个hook中可以学到这种发布订阅 的思路,也可以学到单独的数据拦截这种思路

vueuse 是一个很好的 学习vue3,甚至可以说是学习 开发思路 的一个很棒的库



文章评论

未显示?请点击刷新