likes
comments
collection
share

fetch + 发布订阅 打造全新的 请求方式 🚀

作者站长头像
站长
· 阅读数 34

写在前面

一般我们在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 流程图

fetch + 发布订阅 打造全新的 请求方式  🚀

具体方法 🎈

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

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)
  1. 拦截器 请求拦截器 就是对 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,甚至可以说是学习 开发思路 的一个很棒的库

转载自:https://juejin.cn/post/7211033231058042938
评论
请登录