请求新姿势

在以前的请求中,我们需要定义loading,error,data等字段来接收请求回来的数据,然后通过自己对data的处理获取对应的格式。我尤其是不喜欢loading,和error的定义,因为它是一个不影响我逻辑的代码,但是占用了声明和赋值两个地方,那么有没有办法解决这些问题呢?随着Vue3的兴起,我们可以利用新特性来一波更新,先来看看下面的使用文档,你是否中意!

【Git 项目地址 】

HTTP 请求

|-- http                // 请求相关内容
    |-- request.ts      // 请求初始化配置
    |-- index.ts        // 导出GET,POST请求方式

使用文档

生成请求 hook

type QueryType = {
  pageSize: number;
  page: number;
  keyword: string;
  userInfo: {
    name: string;
  };
};

type ResponseType = {
  list: Array<{ name: string; age: number; icon: string }>;
};

type OtherQueryType = {
  pageSize: Ref<number>;
  page: Ref<number>;
  keyword: Ref<string>;
  userInfo: {
    name: string;
  };
};


export const useMenuList = createHttpGetFactory<OtherQueryType, ResponseType>(
  "/menu/list"
);


export const useUpdateUserInfo = createHttpPostFactory<QueryType, ResponseType>(
  "/user/register"
);

调用请求 hook

分散型参数

import { useMenuList } from "@/api/common";
import { ref } from "vue";

const page = ref(1);
const pageSize = ref(10);
const keyword = ref("1");
const userInfo = reactive({
  name: "test",
});


const [{ loading, data, error }, { abortController, execute }] = useMenuList({
  enabled: (newVal) => newVal.page.value === 1,
  watchKeys: ["page", "pageSize", "userInfo"],
  query: {
    page: page,
    pageSize: pageSize,
    keyword: keyword,
    userInfo: userInfo,
  },
  onSuccess: (data) => {
    console.log(data);
    return data.list[0];
  },
});

const cancel = () => {
  abortController.cancel();
};

const handleSizeChange = (val: number) => {
  params.pageSize = val;
  params.page = 1;
  execute();
};

集中型参数

import { useUpdateUserInfo } from "@/api/common";
import { reactive } from "vue";
// 参数二模式
const params = reactive({
  page: page,
  pageSize: 10,
  keyword: "1",
  userInfo: {
    name: "test",
  },
});
const [{ loading, error }, { execute }] = useUpdateUserInfo({
  query: params,
  successTips: "注册成功",
  onSuccess: (data) => {
    console.log(data);
    return "info";
  },
});


const submit = async () => {
  const res = await execute(
    { keyword: "我的新参数" },
    { message: "一下子就成功了" }
  );
  if (res) {
    let [data, localData] = res;
    console.log("哈哈哈", data, localData);
  }
};

导出数据介绍

GET 请求

  • 数组第一个元素:{ loading, data, error }

    • loading:是否 loading 状态,具有响应式
    • data:数据,具有响应式。类型默认由 ResponseType 决定,但是如果提供 onSuccess 函数,那么类型就会由 onSuccess 返回值类型决定
    • error: 请求是否报错,具有响应式
  • 数组第二个元素: { abortController, execute }

    • execute:手动请求方法,可以传参进去进行原始参数覆盖,支持 async – await 获取返回值,返回值为数组,第一个元素为 onSuccess 处理后的数据,第二个元素为未经过 onSuccess 处理的原始数据
    const submit = async () => {
      const res = await execute({ keyword: "新数据" });
      if (res) {
        let [data, localData] = res;
        console.log("哈哈哈", data, localData);
      }
    };
    
    • abortController:取消请求的对象,调用 abortController.cancel()即可取消当前请求

POST 请求

导出内容基本与 GET 请求相同,有以下几个不同点

  • 不提供 data 参数,如果需要对 data 进行处理可以通过 onSuccess 回调处理
  • 因为 GET 请求不提供弹窗提示功能所以只有一个参数,但是 POST 提供提示功能,所以 execute 方法提供第二个参数,可以覆盖默认提示{message?: string,title?: string}

入参介绍

  • createHttpGetFactory 和 createHttpGetFactory 入参

/**

  • T: 入参类型限制
  • D: 原始data类型
  • arg1: get或者post请求参数
  • arg2: 配置请求方式
  • arg3: get或者post请求的配置,请求头等配置
  • useGetRequest:生成的请求hook
    */
    const useGetRequest = createHttpGetFactory<T, D>(arg1, arg2, arg3);
    const usePostRequest = createHttpPostFactory<T, D>(arg1, arg2, arg3);

useGetRequest 入参

const [{data}] = useGetRequest({
  enabled: bool | ((newVal, oldVal) => bool),
  query: T,
  watchKeys: Array<string>,
  onSuccess: (data: D) => {
    return data.list[0];
  },
  onError: (error) => void,
  onFinally: (stopLoading: () => void) => void
});
  • enabled: 是否请求:true 请求,false 不请求,可以传入 bool 类型,也可以传入一个函数,参数是新值和老值【这里的新老值就是 watch 的新老值,因为对象的特殊性,可能新值和老值一样】
  • query: 请求参数,如果希望能够监听到变化,需要放入响应式数据,Ref 在内部会自动取值使用
  • watchKeys: 监听 query 的 key 值数组,放在这里面的 key 都会受到 watch 监听,一旦有变化就会请求数据,但是还是会受到 enabled 限制
  • onSuccess:请求成功回调,如果不提供 data 返回值类型为创建的 D 类型,如果提供该回调, data 类型变为该函数的返回值类型
  • onError: 错误时调用的函数
  • onFinally:请求结束时调用的函数,带有一个函数【stopLoading】,如果提供了该函数,则需要手动调用 stopLoading 函数修改 loading 状态

usePostRequest 入参

const [{ loading, error }, { execute, abortController }] = useUpdateUserInfo({
  query: params,
  successTips: "注册成功",
  onSuccess: (data: D) => {
    return "info";
  },
  onError: (error) => void,
  onFinally: (stopLoading: () => void) => void
});

大部分与 GET 类似,一下列出特殊点

  • successTips: 提示信息,不提供不弹出成功弹窗
  • enabled: 不提供该参数,所有请求执行都通过 execute 实现
  • watchKeys:不提供该参数
  • execute: 该方法提供第二个参数,可覆盖 successTips 的提示,具体可见返回值部分 execute 介绍

如果你对上面的请求方式感兴趣的话,我们可以一起来看看项目中的代码,其实很简单,但还是简单一起看一下

项目目录说明

|-- Hook-Http-Request
    |-- .env 
    |-- package.json  
    |-- README.md 
    |-- vite.config.ts
    |-- .vscode
    |   |-- extensions.json
    |-- apifox
    |   |-- 动态路由.apifox.json
    |-- public
    |   |-- favicon.ico
    |-- src
    |   |-- App.vue
    |   |-- main.ts
    |   |-- api
    |   |   |-- common
    |   |       |-- index.ts 
    |   |-- utils
    |       |-- http
    |           |-- index.ts
    |           |-- README.md
    |           |-- request.ts
    |-- typings
        |-- utils
            |-- http
                |-- index.d.ts

重点文件

  • apifox:接口相关文件
  • api: 接口请求文件
  • utils/http:http请求工具类
    • request.ts: 基本配置,这里对请求回来的数据蜕了两层,做的很简单,因为不是重点。具体情况看你的项目需求
    • index.ts: 创建请求的工厂函数
    • README:使用说明书
  • typings/utils/http/index.d.ts: 类型文件

dealwithQuery

/**
 * 将ref声明的参数处理成正常参数,而不是对象
 * @param query
 * @returns
 */
function dealwithQuery<Request>(query?: HttpRequestQueryType<Request>) {
  let params: { [key: string]: any } = {};
  if (query) {
    for (const key in query) {
      if (Object.prototype.hasOwnProperty.call(query, key)) {
        const info = query[key];
        params[key] = isRef(info) ? info.value : info;
      }
    }
  }
  return params;
}

此方法,主要是处理使用ref包裹过来的参数,保证参数的正确性

isRef: 判断是否为Ref包裹的数据

createHttpGetFactory

export function createHttpGetFactory<
  Request extends { [key: string]: Ref<any> | any },

  Response

>(url: string, type: "get" | "delete" = "get", options: AxiosRequestConfig<Request> = {}) {
  // 生产一个hook函数

  return function useGetRequest<Transform = Response>({
    enabled,
    query,
    watchKeys,
    onSuccess = (data: any) => data,
    onError = (_error: any) => { },
    onFinally = (stopLoading: () => void) => stopLoading(),
  }: CreateHttpGetFactoryRequest<
    Request,
    Response,
    Transform
  >): CreateHttpGetFactoryResponse<Request, Response, Transform> {
    // tips1: 定义三个变量作为结构内容使用
    const loading = ref(false);
    const error = ref(false);
    const data = ref<Transform | null>(null);


    // 取消请求
    let abortController: AbortControllerType = {
      cancel: () => { },
      target: null,
    };
	// tips2: 发起请求获取数据
    const getDataToServer = (
      params?: Request
    ): Promise<[Response | Transform, Response] | void> => {
      abortController.target = new AbortController();
      abortController.cancel = () => abortController.target?.abort();
      loading.value = true;
      const queryInfo = dealwithQuery(query);
      const paramsInfo = dealwithQuery(params);
      return httpRequest[type](url, {
        params: { ...paramsInfo, ...queryInfo },
        signal: abortController.target.signal,
        ...options,
      })
        .then((res: any) => {
          let dealwithData = onSuccess(res);
          data.value = dealwithData;
          return [dealwithData, res] as [Response | Transform, Response];
        })
        .catch((_error) => {
          error.value = true;
          onError(_error);
        })

        .finally(() => {
          onFinally(() => (loading.value = false));
        });
    };

    // tips3: 传入新参数 重新请求
    const execute = (query?: Request) => getDataToServer(query);

    const enabledIsBol = typeof enabled === "boolean";
    // tips4: 监听参数改变时重新发请求
    watch<Request, true>(
      () => {
        let watchInfo: any = {};
        watchKeys?.forEach((key: string) => {
          watchInfo[key] = query[key];
        });
        return watchInfo;
      },

      (newVal, oldVal) => {
        const isRequest = enabledIsBol ? enabled : enabled(newVal, oldVal);
        if (isRequest) {
          getDataToServer(newVal);
        }
      },
      {
        immediate: true,
        deep: true,
      }
    );

    return [
      {
        loading: loading,
        data: data,
        error: error,
      },
      { execute, abortController },
    ];
  };
}

tips1: 定义三个变量作为结构内容使用

这里主要定义loading,error, data, abortController四个变量

  • loading:加载标识符
  • error:加载错误的标识符
  • data:数据源
  • abortController:请求控制对象,用于请求取消等操作

tips2: 发起请求获取数据

这里主要是看onSuccess回调,这个回调将onSuccess返回的值赋值给了data,然后return 一个数组【[onSuccess处理后数据,原始数据]】, 返回值主要是服务于execute

tips3: 传入新参数 重新请求

这里对外提供一个execute方法,这个方法主要是可控请求,并且传入参数会覆盖当前参数。

tips4: 监听参数改变时重新发请求

get请求一般是属于获取数据,主要就是看参数的变化来触发请求,所以根据提供的watchKeys直接监听参数变化,变化时就重新请求,但是也有时候参数变化到某个值得时候并不想请求。就需要查看enabled参数是否为true。

难点:

这里唯一的难点其实是类型推断上,如何让类型推断从useMenuList中的Response类型到onSuccessTransform类型,这里主要是让Transform = Response,然后data类型定义为Ref<Transform>

createHttpPostFactory

export function createHttpPostFactory<
  Request extends { [key: string]: Ref<any> | any },

  Response

>(url: string, type: "post" | "put" = "post", options: AxiosRequestConfig<Request> = {}) {
  // 生产一个hook函数

  return function usePostRequest<Transform = Response>({
    query,
    successTips = "请求成功",
    onSuccess = (_data: Response) => _data,
    onError = (_error) => { },
    onFinally = (stopLoading) => stopLoading(),
  }: CreateHttpPostFactoryRequest<
    Request,
    Response,
    Transform
  >): CreateHttpPostFactoryResponse<Request, Response, Transform> {
    // 定义三个变量作为结构内容使用
    const loading = ref(false);
    const error = ref(false);


    // 取消请求
    let abortController: AbortControllerType = {
      cancel: () => { },
      target: null,
    };


    const postDataToServer = (
      params?: Request,
      tips?: successTipsType
    ): Promise<[Response | Transform, Response] | void> => {
      abortController.target = new AbortController();
      abortController.cancel = () => abortController.target?.abort();
      loading.value = true;
      const queryInfo = dealwithQuery(query);
      const paramsInfo = dealwithQuery(params);
      return httpRequest[type]<ResponseDataType<Response>>(
        url,
        { ...paramsInfo, ...queryInfo },
        {
          signal: abortController.target.signal,
          ...options,
        }
      )
        .then((res: any) => {
          if (tips || successTips) {
            alert( "请求成功");
          }
          const dealwithData = onSuccess(res);
          return [dealwithData, res] as [Response | Transform, Response];
        })

        .catch((_error) => {
          error.value = true;
          onError(_error);
        })
        .finally(() => {
          onFinally(() => (loading.value = false));
        });
    };

    // 传入新参数 重新请求
    const execute = (query?: Request, tips?: successTipsType) =>
      postDataToServer(query, tips);

    return [
      {
        loading: loading,
        error: error,
      },

      { execute, abortController },
    ];
  };
}

post请求大部分同get请求,但是因为post往往是手动控制,所以这里并不提供监听这种操作

post请求后一般情况下都会有一个成功提示,所以这边就默认给了一个提示能力

最后

大致实现就是这样,其实整体代码实现并不难,唯一难点就是类型推断上,也可能是我对ts不熟悉导致的。

其实现在已经有实现该功能的库了:【alova】

不过又有什么关系呢!我们自己实现是不是更灵活呢!哈哈哈

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYEAYh3Y' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片