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

阿轩的BUG
文章191
评论56
标签101
站点日志
作者总数:1位
置顶文章:0篇
标签总数:101条
文章总数:196篇
评论总数:56条
微语总数:201条
运行天数:2317天
最近更新:2026-07-28
文章标签
最新评论
Sherlock
2025-08-21
不是应该打包成apk文件吗,怎么突然就运行编译了,下一步呢
老金er
2025-05-25
人生设计非常必要,在信息化发达的今天,周围大部分人依然只能过着人生工程模式生活,长时间生活洗礼后,尽管“人生工程”已经不再适合很多人,他们也无法做出任何改变,人生设计给我们提供了新的思路,值得尝试
赏帮赚
2025-05-25
职场太多的尔虞我诈
三笑
2024-08-16
emlog6.0.1可以升级支持吗?
pony
2024-07-08
这篇文章提供了一种新的人生规划方法——"人生设计",它借鉴了产品设计的理念,鼓励人们以创造性和探索性的方式思考和规划自己的生活。以下是对这篇文章的几个评价维度: 创新性:将产品设计的理念应用于人生规划是一个新颖的视角,为人们提供了一种跳出传统生活轨迹,探索个性化生活路径的方法。 实用性:文章不仅提出了理念,还详细介绍了具体的实施步骤,包括自我评估、制定计划、原型设计和实践选择等,具有很强的操作性。 启发性:通过强调好奇心、不断尝试、重新定义问题等心态,文章鼓励人们打破常规,勇于探索未知,这对于激发个人潜能和创造力具有积极作用。 适应性:"人生设计"理念适用于不同人生阶段和不同背景的人,无论是希望从头开始的人,还是已经取得一定成就但希望探索新方向的人,都可以从中获得启发。 情感共鸣:文章对许多人对传统生活轨迹的不满和对改变的渴望进行了深刻的洞察,能够引起读者的共鸣。 可持续性:人生设计强调的是一个持续迭代和试错的过程,这与现实生活的发展规律相吻合,有助于人们在不断变化的环境中做出适应性调整。 教育意义:作为一种人生规划方法,"人生设计"可以作为教育的一部分,帮助年轻人更早地学会自我探索和规划,为未来的生活和职业发展打下基础。 局限性:尽管"人生设计"提供了一种新的思考框架,但它可能需要个人具备一定的自我认知和反思能力,对于一些缺乏自我探索经验的人来说,可能需要额外的指导和支持。 总体来说,这篇文章提供了一种有价值的人生规划方法,对于那些渴望改变、寻求个性化生活的人来说,是一种有益的参考和指导。
首页实用干货 🌋

写在前面

一般我们在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,甚至可以说是学习 开发思路 的一个很棒的库

上一篇
⚔️ 代码评审的18个军规
下一篇
🌌 一次性搞懂 Ajax、Fetch 和 Axios 的区别