设计vue3的请求实体工厂

设计一个vue3的请求实体工厂

描述

PS: 这里的方法是基于Vue的class写法的,对于setup写法不适用。

主要功能是创建一个具备一个请求完整封装的实例,可以便捷的请求,取消请求,获取数据和请求状态等功能

下面是通过typescript和Vue实现,结合typescript的声明,这个generateRequest方法才会得到升华

实现

实现分下面3个部分

1.构建一个基础请求方法

2.创建具体请求的方法

3.generateRequest对请求的封装

构建一个基础请求方法

这里没啥说的,直接上axios做一个简单的封装

request.ts

import axios from 'axios'
const option = {...}
export request = axios.create(option)

创建具体请求的方法

下面是对请求的声明文件

xxx.d.ts

// 对请求返回的请求体声明
export class Result<T0 = any> {
    /** code */
    code?:number;

    /** data */
    data?:T0;

    /** error_code */
    error_code?:string;

    /** error_message */
    error_message?:string;

    /** success */
    success?:boolean;
}
// 文章接口返回data声明
export interface ArticleData {
    id:number;
    title:string;
    cover_url:string;
    content:string;
    read_count:string;
    abstract:string;
    time:number;
}

下面是请求的定义

api.ts

import { request } from './request'
import { AxiosRequestConfig } from 'axios'
import { Result, ArticleData } from './xxx'
/** 获取文章 */
export const getArticle = (id:number, config?:AxiosRequestConfig) => request.get<Result<ArticleData>>(`/api/article?id=${id}`)

generateRequest对请求的封装

这里主要用到的知识点有:

1.由映射类型进行推断(对参数进行拆包)

2.对上下文的理解

3.vue数据响应式机制

下面是一个基础实现,还可以继续封装对业务场景有用的方法或属性

功能

  • 具备响应式属性:data(返回的数据)loading、isError、params(请求的参数)
  • 可以传入数据处理的方法format
  • 设置data初始值initData
  • 取消请求的cancel方法
  • 发起请求的run方法

utils.ts

import axios, { AxiosPromise, AxiosRequestConfig } from 'axios';
interface GenerateRequestHook<T, K> {
  data:T | undefined;
  run:(params?:K) => AxiosPromise<T>;
  loading:boolean;
  isError:boolean;
  uid:number;
  cancel:((msg?:string) => void);
  params:K | undefined;
}
interface Config<L> {
  config?:AxiosRequestConfig;
  format?:(data:any) => any;
  initData?:L;
}
let _uid = 0;
/**
 * @description 返回请求的封装实体的request,仅可以在class写法的组建内使用
 * @template T
 * @template K
 * @param {(params?:K, config?:AxiosRequestConfig) => AxiosPromise<T>} reqFunc
 * @param {K} [params]
 * @param {Config<T>} [config]
 * @returns {GenerateRequestHook<T>}
 */
export const generateRequest = <T, K>(reqFunc:(params?:K, config?:AxiosRequestConfig) => AxiosPromise<T>, params?:K, config?:Config<T>):GenerateRequestHook<T, K> => {
  const uid = _uid++;
  const source = axios.CancelToken.source();
  return {
    run: async function(_params?:K) {
      // eslint-disable-next-line @typescript-eslint/no-this-alias
      const self:GenerateRequestHook<T, K> = this;
      if (!self || uid !== self.uid) {
        throw Error('[generateRequest] The context exception');
      }
      try {
        self.isError = false;
        self.loading = true;
        if (_params) {
          self.params = _params;
        }
        const res = await reqFunc(_params || params, {
          cancelToken: source.token,
          ...config?.config,
        });
        // 格式化返回数据
        if (config?.format) {
          config.format(res.data);
        }
        self.data = res.data;
        return res;
      } catch (error) {
        self.data = config?.initData;
        self.isError = true;
        return Promise.reject(error);
      } finally {
        self.loading = false;
      }
    },
    data: config?.initData,
    loading: false,
    isError: false,
    uid,
    cancel: source.cancel,
    params,
  };
};

使用demo

<template>
  <div class="article_info">
  </div>
</template>
<script lang="ts">
import { Vue, Options } from 'vue-property-decorator';
import { generateRequest } from './utils';
import { getArticle } from './api';

@Options({})
export default class extends Vue {
  articleReq = generateRequest(getArticle)
  created() {
    this.articleReq.run(1).then((res) => {
      document.title = this.title;
    }).catch((err) => {
      console.error(err.message);
    });
  }
}
</script>

上面的this.articleReq会包含的属性,我们可以直接使用loadingdataisError,而且这些属性都是具备响应式的,而且data属性是根据传入getArticle推断出数据类型,十分方便。可以通过cancel方法取消请求,run可以发起请求。

上面我的run方法内有这么一段判断,因为run方法内部是用了this来进行数据更新,让属性具备响应式。不过相对的限制就是不能修改this.articleReq.run调用时的上下文。这样会导致获取属性异常。不过这个限制基本没什么影响,好处是大于坏处的。所以我给每个请求分配一个uid,用于判断请求run方法的上下文是否一致。

if (!self || uid !== self.uid) {
    throw Error('[generateRequest] The context exception');
}

结语

上面generateRequest创建了一个具备响应式的请求实例,实现了从请求参数、请求数据都具备声明,且可以直接在组件内使用具备响应式的属性。目前来说还是挺方便的。