TS泛型实现vue3下的useHooks+axios

38 阅读5分钟

泛型

介绍

考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型

泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。

当我们需要写一个传入什么类型就得到什么类型的函数

  • 使用any类型会导致这个函数可以接收任何类型的arg参数,这样就丢失了一些信息

     function one(a: any) : any{
       return a;
     }
     ​
     //每一种类型都写一个方法
     function one(a: any) : any{
         if(typeof a === 'number') {
             let ret = (a as number)
             return ret ;
         }
         return a;
     }
    
  • 使用类型变量

     function one<T>(a: T) : T{
         return a;
     }
     let a1 = one<number>(1)
     let a2 = one(520)
     //描述T是什么类型的时候,可以在<number>描述它是一个number类型,
     //也可以类型推论
    

泛型类型

泛型函数的类型与非泛型函数的类型没什么不同,只是有一个类型参数在最前面,像函数声明一样:

 function identity<T>(arg: T): T {
     return arg;
 }
 ​
 let myIdentity: <T>(arg: T) => T = identity;

我们也可以使用不同的泛型参数名,只要在数量上和使用方式上能对应上就可以。

 function identity<T>(arg: T): T {
     return arg;
 }
 ​
 let myIdentity: <U>(arg: U) => U = identity;

我们还可以使用带有调用签名的对象字面量来定义泛型函数:

 function identity<T>(arg: T): T {
     return arg;
 }
 ​
 let myIdentity: {<T>(arg: T): T} = identity;

这引导我们去写第一个泛型接口了。 我们把上面例子里的对象字面量拿出来做为一个接口:

 interface GenericIdentityFn {
     <T>(arg: T): T;
 }
 ​
 function identity<T>(arg: T): T {
     return arg;
 }
 ​
 let myIdentity: GenericIdentityFn = identity;

一个相似的例子,我们可以把泛型参数当作整个接口的一个参数。 这样我们就能清楚的知道使用的具体是哪个泛型类型(比如: Dictionary<string>而不只是Dictionary)。 这样接口里的其它成员也能知道这个参数的类型了。

 interface GenericIdentityFn<T> {
     (arg: T): T;
 }
 ​
 function identity<T>(arg: T): T {
     return arg;
 }
 ​
 let myIdentity: GenericIdentityFn<number> = identity;

泛型变量使用

如果我们想同时打印出arg的长度。 我们很可能会这样做:

 function loggingIdentity<T>(arg: T): T {
     console.log(arg.length);  // Error: T doesn't have .length
     return arg;
 }

如果这么做,编译器会报错说我们使用了arg.length属性,但是没有地方指明arg具有这个属性。 记住,这些类型变量代表的是任意类型,所以使用这个函数的人可能传入的是个数字,而数字是没有 .length属性的。

现在假设我们想操作是T类型的数组而不直接是T。由于我们操作的是数组,所以.length属性是应该存在的。 我们可以像创建其它数组一样创建这个数组:

 function loggingIdentity<T>(arg: T[]): T[] {
     console.log(arg.length);  // Array has a .length, so no more error
     return arg;
 }
 ​
 function loggingIdentity<T>(arg: Array<T>): Array<T> {
     console.log(arg.length);  // Array has a .length, so no more error
     return arg;
 }

泛型约束extends

我们有时候想操作某类型的一组值,并且我们知道这组值具有什么样的属性。 在 loggingIdentity例子中,我们想访问arglength属性,但是编译器并不能证明每种类型都有length属性,所以就报错了。

 function loggingIdentity<T>(arg: T): T {
     console.log(arg.length);  // Error: T doesn't have .length
     return arg;
 }

相比于操作any所有类型,我们想要限制函数去处理任意带有.length属性的所有类型。 只要传入的类型有这个属性,我们就允许,就是说至少包含这一属性。 为此,我们需要列出对于T的约束要求。

为此,我们定义一个接口来描述约束条件。 创建一个包含 .length属性的接口,使用这个接口和extends关键字来实现约束:

 interface Lengthwise {
     length: number;
 }
 ​
 function loggingIdentity<T extends Lengthwise>(arg: T): T {
     console.log(arg.length);  // Now we know it has a .length property, so no more error
     return arg;
 }

现在这个泛型函数被定义了约束,因此它不再是适用于任意类型:

 loggingIdentity(3);  // Error, 类型“number”的参数不能赋给类型“Lengthwise”的参数。

我们需要传入符合约束类型的值,必须包含必须的属性:

 loggingIdentity({length: 10, value: 3});

泛型类

泛型类看上去与泛型接口差不多。 泛型类使用( <>)括起泛型类型,跟在类名后面。

 class GenericNumber<T> {
     zeroValue: T;
     add: (x: T, y: T) => T;
 }
 ​
 let myGenericNumber = new GenericNumber<number>();
 myGenericNumber.zeroValue = 0;
 myGenericNumber.add = function(x, y) { return x + y; };
 console.log(myGenericNumber.add(1,2))

类有两部分:静态部分和实例部分。 泛型类指的是实例部分的类型,所以类的静态属性不能使用这个泛型类型。

案例

  • 函数调用时确定参数类型
 const fn = <T, U>(a: T, b?: U): T => {
   console.log(a, b);
   return a;
 }
 let a = fn<number, string>(1, '2');
  • 泛型接口
 interface GenericIdentityFn<T, U> {
   (a: T, b?: Array<U>): T;
 }
 const fn1: GenericIdentityFn<number, string> = (a, b) => {
   console.log(a, b);
   return 1
 }
 fn1(1,['2']);

泛型vue3+useHooks+axios

  •  //.d.ts
     import type { AxiosResponse } from 'axios';
     export interface AxiosResponse<T = any, D = any> {
       data: T;
       status: number;
       statusText: string;
       headers: RawAxiosResponseHeaders | AxiosResponseHeaders;
       config: InternalAxiosRequestConfig<D>;
       request?: any;
     }
     type ApiResponse<T = any> = {
       success: boolean;
       data: T;
       code: string;
       message: string;
     };
     type HttpResponse<T = any> = AxiosResponse<ApiResponse<T>>;
     type HttpResponseP<T = any> = Promise<HttpResponse<T>>;
    
     //api
     static getAttrList(data: any): HttpResponseP<string> {
       return axios.post('/elementAttr/getElementAllList', data);
     }
    
     //vue3
     const searchFormData = ref({
       pageNum: 1,
       pageSize: 10,
       elementId: '',
       attrName: ''
     });
     interface FormDataProp {
       pageNum: number;
       pageSize: number;
       elementId: boolean;
       attrName: number;
     }
     //将FormDataProp作为泛型变量
     const { requestFn: getAttrList } = useRequest<FormDataProp, any>(DataOrgSceneController.getAttrList);
     //searchFormData将会和FormDataProp进行TS检测
     getAttrList(searchFormData.value).then((res: any) => {});
    
     interface Opt<T> {
       initData?: T;
       message?: string; // 成功默认提示
       errMessage?: boolean | string; // 失败默认toast提示
     }
     type RequestsType<T = any> = [T];//此时T作为FormDataProp
     ​
     /**
      * 
      * ReqProps: 请求参数的类型接口
      * ResProps:响应参数data的类型接口
      * @param apiFn
      * ...arg剩余参数是数组的形式,这里是为了方便以后扩展
      * @param opt 
      * @returns 
      */
     export default function useRequest<ReqProps, ResProps extends any>(apiFn: (...arg: RequestsType<ReqProps>) => HttpResponseP<ResProps>, opt?: Opt<ResProps>) {
       const options = {
         errMessage: true,
         ...opt,
       };
       const loading = ref(false);
       const data = ref(options.initData as Resp) as Ref<Resp>;
       const error = ref(null) as Ref<any>;
     ​
       const showError = (message: string, code?: string, exceptionType?: number) => {
       };
     ​
       function requestFn(...arg: RequestsType<Requests>): Promise<ApiResponse<Resp>> {
         loading.value = true;
         return apiFn(...arg)
           .then((response) => {
             return response.data;
           })
           .catch((err: HttpError) => {
             return Promise.reject(err);
           })
           .finally(() => {
             loading.value = false;
           });
       }
     ​
       return {
         loading,
         data,
         error,
         requestFn,
       };
     }