精华内容
下载资源
问答
  • typescriptinfer的理解与使用

    千次阅读 2020-08-28 07:45:43
    前言 以前一直不会用infer,要么直接就是returnType,压根不需要用infer,网上那些教程只给示例不给具体场景就无法让人很好理解这玩意,并且很好

    前言

    • 以前一直不会用infer,要么直接就是returnType,压根不需要用infer,网上那些教程只给示例不给具体场景就无法让人很好理解这玩意。

    类型分发

    • 对于infer,最好应该先说一下类型分发,虽然这2关系不是太大,但是如果把infer与类型分发结合起来,让人一看就觉得这人ts水平可以。至于协变与逆变等概念会比较容易让人搞混乱,可以以后再掌握。
    • 我以前也学过这个,但是并不是能完全掌握它的使用时机,也不知道如何用,所以看别人用能看懂和自己能用完全是2种状态。
    • 首先看一下类型分发的基本例子:
    interface Fish {
        fish: string
    }
    interface Water {
        water: string
    }
    interface Bird {
        bird: string
    }
    interface Sky {
        sky: string
    }
    //naked type
    type Condition<T> = T extends Fish ? Water : Sky;
    
    let condition1: Condition<Fish | Bird> = { water: '水' };
    let condition2: Condition<Fish | Bird> = { sky: '天空' };
    
    • 相信这个例子大家很容易理解,但是实际中什么时候用,怎么用,完全不知道。
    • 这个例子有个特点,就是下面的condition1和condition2里定义的类型里所传的泛型与后面赋值的类型并不一样。
    • 也就是说,类型分发一般是用来先知道已知类型,赋的值的类型会基于这个分发进行判断推出相应类型。
    • 乍看之下好像还是没什么卵用,比如condition1,我都知道类型,我直接写个Sky|Water类型不香?干嘛二货的还搞个类型分发?
    • 上面那个例子确实没啥卵用,但是如果判断继承的也是泛型,那么就可以快速取出一些类型,而不用自己重新去定义:(虽然这些很多都是内置的)
    type Diff<T, U> = T extends U ? never : T;
    
    type R = Diff<"a" | "b" | "c" | "d", "a" | "c" | "f">;  // "b" | "d"
    
    type Filter<T, U> = T extends U ? T : never;
    type R1 = Filter<string | number | boolean, number>;
    
    • 既然有内置的,还不是没卵用。。。所以这就需要和infer联合使用才能看出牛b之处。

    infer

    • infer大家应该都知道,returnType就是infer搞得,代码是这样:
    type ReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R ? R : any;
    
    • 乍看之下好像有点难懂,其实仔细看发现还是很好理解的,它也是个类型分发。
    • 学到这里,很多人可能就只知道有这个东西,但是什么时候用Infer完全不知道,我也是这样,后来再次听课时突发灵感,发现这个infer其实就相当于占位,也就是一个不知道的类型,用infer X去给他占位,再结合类型分发,就能玩出花样来了。当时还有小伙伴这么问:ts不是能自动推断类型吗?为什么需要Infer X去推断类型。卧槽,这个问的太好了,这个就是理解Infer的关键。
    • 我们先结合个示例来进行说明:
    export {}
    type Parameters<T> = T extends (...args: infer R) => any ? R : any;
    type T0 = Parameters<() => string>;  // []
    type T1 = Parameters<(s: string) => void>;  // [string]
    type T2 = Parameters<(<T>(arg: T) => T)>;  // [unknown]
    
    • 这个parameter也是内置的,可以看见,也是个类型分发,跟returnType区别就是infer X的占位跑到参数上去定义类型了。
    • 如果我们把infer R换成已知类型,那么这个类型分发就跟一开始的demo没太大区别:
    type Parameters<T> = T extends (...args:string[]) => any ? string[] : any;
    type T0 = Parameters<() => string>; 
    
    • 如果不换成已知类型,那么只写R不写infer会报错,因为不知道R是什么东西。
    • 那么如果通过泛型传呢?可惜args必须是个数组类型,所以用泛型传还得限定下它的条件:
    type Parameters<T,R extends Array<any>> = T extends (...args:R) => any ? R : any;
    
    type T0 = Parameters<() => string,string[]>; 
    
    • 可以发现,这么传跟已知类型传其实没太大区别,因为在传第二个泛型的时候,这个类型我们是知道的,所以这种情况,也没什么太大用处,除非传泛型的是另一个人,那么我们在写这个库的时候,倒是可以拿到用户所定义的类型。这时倒是有点作用。
    • 这样一换就可以发现,infer可以在类型推导中去占任何位置,最后的推导的类型可以借助这之间所需的类型。可以看下这个例子加深理解:
      type T1 = { name: string };
      type T2 = { age: number };
      
      type UnionToIntersection<T> = T extends { a: (x: infer U) => void; b: (x: infer U) => void } ? U : never;
      type T3 = UnionToIntersection<{ a: (x: T1) => void; b: (x: T2) => void }>; // T1 & T2
    
    interface Action<T> {
        payload?: T;
        type: string;
      }
      
      class EffectModule {
        count = 1;
        message = "hello!";
      
        delay(input: Promise<number>) {
          return input.then(i => ({
            payload: `hello ${i}!`,
            type: 'delay'
          }));
        }
      
        setMessage(action: Action<Date>) {
          return {
            payload: action.payload!.getMilliseconds(),
            type: "set-message"
          };
        }
      }
      
      // 修改 Connect 的类型,让 connected 的类型变成预期的类型
      type Connect = (module: EffectModule) => any;
      
      const connect: Connect = m => ({
        delay: (input: number) => ({
          type: 'delay',
          payload: `hello 2`
        }),
        setMessage: (input: Date) => ({
          type: "set-message",
          payload: input.getMilliseconds()
        })
      });
      
      type Connected = {
        delay(input: number): Action<string>;
        setMessage(action: Date): Action<number>;
      };
      
      export const connected: Connected = connect(new EffectModule());
    
    • 要求修改那个any,使其返回正确类型,而且这个类型要和connected一样。
    • 有同学说,直接把any改成connected不就完了?要是这么简单也不会出这题。这个肯定是要你推出来,并且这个connected它的类型是EffectModule实例上的方法,里面的参数与返回还修改了。
    • 这题怎么做呢,先一步步来,先提取出effectModule的方法,不然没法下一步。
    • 提取class方法没有现成的,肯定不能keyof EffectModule,因为还有别的东西,怎么排除别的玩意呢?就是利用类型分发和class可以取值来做,如果是函数,那就提取,否则就不提取:
      type MethodName<T> = {[F in keyof T]:T[F] extends Function ? F:never}[keyof T]
      type EE =  MethodName<EffectModule>
    
    • 这里同时利用value如果是never 则keyof就不会返回。这段其实挺有启发性,因为很多时候,都想搞个循环判断类型,然后进行选择,这就是个很好的范例。
    • 拿到了name然后要改装方法它需要:
    asyncMethod<T, U>(input: Promise<T>): Promise<Action<U>>  变成了
    asyncMethod<T, U>(input: T): Action<U> 
    syncMethod<T, U>(action: Action<T>): Action<U>  变成了
    syncMethod<T, U>(action: T): Action<U>
    
    • 这个是题目给的,直接抄来:
    type asyncMethod<T, U> = (input: Promise<T>) => Promise<Action<U>> 
    type asyncMethodConnect<T, U> = (input: T) => Action<U> 
    type syncMethod<T, U> = (action: Action<T>) => Action<U> 
    type syncMethodConnect<T, U> = (action: T) => Action<U> 
    
    • 然后需要做一个类型分发,用来判断是哪个方法,再分发给哪个方法:
    type EffectMethodAssign<T> = T extends asyncMethod<infer U, infer V>? 
                                                asyncMethodConnect<U, V>
                                            : T extends syncMethod<infer U, infer V>
                                            ? syncMethodConnect<U, V>
                                            : never
    
    • 这段很简单,就是分发判断,泛型是用infer占位ok。
    • 最后,修改connect,就大功告成
      type Connect = (module: EffectModule) => {
          [F in MethodName<typeof module>]:EffectMethodAssign<typeof module[F]>
      };
    
    展开全文
  • 下面看一下更难点的例子,来源于leetcode招聘: https://github.com/LeetCode-OpenSource/hire/blob/master/typescript_zh.md 题目是这样: interface Action { payload?: T; type: string; } class EffectModule {...

    往期精彩

    前言

    以前一直不会用infer,要么直接就是returnType,压根不需要用infer,网上那些教程只给示例不给具体场景就无法让人很好理解这玩意。

    类型分发

    对于infer,最好应该先说一下类型分发,虽然他们关系不是太大,但是如果把infer与类型分发结合起来,让人一看就觉得这人ts水平可以。至于协变逆变等概念会比较容易让人搞混乱,可以以后再掌握。

    我以前也学过这个,但是并不是能完全掌握它的使用时机,也不知道如何用,所以看别人用能看懂和自己能用完全是2种状态。

    首先看一下类型分发的基本例子:

    interface Fish {
        fish: string
    }
    interface Water {
        water: string
    }
    interface Bird {
        bird: string
    }
    interface Sky {
        sky: string
    }
    //naked type
    type Condition<T> = T extends Fish ? Water : Sky;
    
    
    let condition1: Condition<Fish | Bird> = { water: '水' };
    let condition2: Condition<Fish | Bird> = { sky: '天空' };
    

    相信这个例子大家很容易理解,但是实际中什么时候用,怎么用,完全不知道。

    这个例子有个特点,就是下面的condition1condition2里定义的类型里所传的泛型与后面赋值的类型并不一样。

    也就是说,类型分发一般是用来先知道已知类型,赋的值的类型会基于这个分发进行判断推出相应类型。

    乍看之下好像还是没什么卵用,比如condition1,我都知道类型,我直接写个Sky|Water类型不香?干嘛二货的还搞个类型分发?

    上面那个例子确实没啥卵用,但是如果判断继承的也是泛型,那么就可以快速取出一些类型,而不用自己重新去定义:(虽然这些很多都是内置的)

    type Diff<T, U> = T extends U ? never : T;
    
    
    type R = Diff<"a" | "b" | "c" | "d", "a" | "c" | "f">;  // "b" | "d"
    
    
    type Filter<T, U> = T extends U ? T : never;
    type R1 = Filter<string | number | boolean, number>;
    

    既然有内置的,还不是没卵用。。。所以这就需要和infer联合使用才能看出牛b之处。

    infer初探

    infer大家应该都知道,returnType就是infer搞得,代码是这样:

    type ReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R ? R : any;
    

    乍看之下好像有点难懂,其实仔细看发现还是很好理解的,它也是个类型分发。

    学到这里,很多人可能就只知道有这个东西,但是什么时候用Infer完全不知道,我也是这样,后来再次听课时突发灵感,发现这个infer其实就相当于占位,也就是一个不知道的类型,用infer X去给他占位,再结合类型分发,就能玩出花样来了。当时还有小伙伴这么问:ts不是能自动推断类型吗?为什么需要Infer X去推断类型。卧槽,这个问的太好了,这个就是理解Infer的关键。

    我们先结合个示例来进行说明:

    export {}
    type Parameters<T> = T extends (...args: infer R) => any ? R : any;
    type T0 = Parameters<() => string>;  // []
    type T1 = Parameters<(s: string) => void>;  // [string]
    type T2 = Parameters<(<T>(arg: T) => T)>;  // [unknown]
    

    这个parameter也是内置的,可以看见,也是个类型分发,跟returnType区别就是infer X的占位跑到参数上去定义类型了。

    如果我们把infer R换成已知类型,那么这个类型分发就跟一开始的demo没太大区别:

    type Parameters<T> = T extends (...args:string[]) => any ? string[] : any;
    type T0 = Parameters<() => string>; 
    

    如果不换成已知类型,那么只写R不写infer会报错,因为不知道R是什么东西。

    那么如果通过泛型传呢?可惜args必须是个数组类型,所以用泛型传还得限定下它的条件:

    type Parameters<T,R extends Array<any>> = T extends (...args:R) => any ? R : any;
    
    
    type T0 = Parameters<() => string,string[]>; 
    

    可以发现,这么传跟已知类型传其实没太大区别,因为在传第二个泛型的时候,这个类型我们是知道的,所以这种情况,也没什么太大用处,除非传泛型的是另一个人,那么我们在写这个库的时候,倒是可以拿到用户所定义的类型。这时倒是有点作用。

    这样一换就可以发现,infer可以在类型推导中去占任何位置,最后的推导的类型可以借助这之间所需的类型。可以看下这个例子加深理解:

      type T1 = { name: string };
      type T2 = { age: number };
      
      type UnionToInterp<T> = T extends { a: (x: infer U) => void; b: (x: infer U) => void } ? U : never;
      type T3 = UnionToInterp<{ a: (x: T1) => void; b: (x: T2) => void }>; // T1 & T2
    

    这个例子就是infer取得参数,两个函数的参数,对于为啥2个会出来交叉类型,这里是协变,所以是交叉类型。

    下面看一下更难点的例子,来源于leetcode招聘:

    https://github.com/LeetCode-OpenSource/hire/blob/master/typescript_zh.md

    题目是这样:

    interface Action<T> {
        payload?: T;
        type: string;
      }
      
      class EffectModule {
        count = 1;
        message = "hello!";
      
        delay(input: Promise<number>) {
          return input.then(i => ({
            payload: `hello ${i}!`,
            type: 'delay'
          }));
        }
      
        setMessage(action: Action<Date>) {
          return {
            payload: action.payload!.getMilliseconds(),
            type: "set-message"
          };
        }
      }
      
      // 修改 Connect 的类型,让 connected 的类型变成预期的类型
      type Connect = (module: EffectModule) => any;
      
      const connect: Connect = m => ({
        delay: (input: number) => ({
          type: 'delay',
          payload: `hello 2`
        }),
        setMessage: (input: Date) => ({
          type: "set-message",
          payload: input.getMilliseconds()
        })
      });
      
      type Connected = {
        delay(input: number): Action<string>;
        setMessage(action: Date): Action<number>;
      };
      
      export const connected: Connected = connect(new EffectModule());
    

    要求修改那个any,使其返回正确类型,而且这个类型要和connected一样。

    有同学说,直接把any改成connected不就完了?要是这么简单也不会出这题。这个肯定是要你推出来,并且这个connected它的类型是EffectModule实例上的方法,里面的参数与返回还修改了。

    这题怎么做呢,先一步步来,先提取出effectModule的方法,不然没法下一步。

    提取class方法没有现成的,肯定不能keyof EffectModule,因为还有别的东西,怎么排除别的玩意呢?就是利用类型分发和class可以取值来做,如果是函数,那就提取,否则就不提取:

      type MethodName<T> = {[F in keyof T]:T[F] extends Function ? F:never}[keyof T]  type EE =  MethodName<EffectModule>
    

    这里同时利用value如果是never 则keyof就不会返回。这段其实挺有启发性,因为很多时候,都想搞个循环判断类型,然后进行选择,这就是个很好的范例。

    拿到了name然后要改装方法它需要:

    asyncMethod<T, U>(input: Promise<T>): Promise<Action<U>> asyncMethod<T, U>(input: T): Action<U> syncMethod<T, U>(action: Action<T>): Action<U>  syncMethod<T, U>(action: T): Action<U>
    

    这个是题目给的,直接抄来:

    type asyncMethod<T, U> = (input: Promise<T>) => Promise<Action<U>> type asyncMethodConnect<T, U> = (input: T) => Action<U> type syncMethod<T, U> = (action: Action<T>) => Action<U> type syncMethodConnect<T, U> = (action: T) => Action<U> 
    

    然后需要做一个类型分发,用来判断是哪个方法,再分发给哪个方法:

    type EffectMethodAssign<T> = T extends asyncMethod<infer U, infer V>      ? asyncMethodConnect<U, V>     : T extends syncMethod<infer U, infer V>     ? syncMethodConnect<U, V>     : never
    

    这段很简单,就是分发判断,泛型是用infer占位ok。

    最后,修改connect,就大功告成.

      type Connect = (module: EffectModule) => {
          [F in MethodName<typeof module>]:EffectMethodAssign<typeof module[F]>
      }
    
    展开全文
  • 理解TypeScript中的infer关键字
    展开全文
  • 首先从一个问题引入,下面有一个模拟发送请求的方法,返回内容被包裹在 Promise 里面返回,我们怎样拿到 Promise 里面包裹的类型呢? type ResponseEntity<T> = { success: ... P : never 参考: TypeScript 高级用法

    首先从一个问题引入,下面有一个模拟发送请求的方法,返回内容被包裹在 Promise 里面返回,我们怎样拿到 Promise 里面包裹的类型呢?

    type ResponseEntity<T> = {
        success: boolean;
        result: T | null;
        message: string;
    }
    
    function request<T>(req: T, timeout: number) {
        return Promise.race([
            new Promise<ResponseEntity<T>>(resolve => setTimeout(() => resolve({
                success: true,
                result: req,
                message: ""
            }), 3000)),
            new Promise<ResponseEntity<T>>((_, reject) => setTimeout(() => reject({
                success: false,
                result: null,
                message: "请求超时"
            }), timeout))
        ])
    }
    

    很多同学上来就用 typeofReturnType 一把梭哈:

    type Request = typeof request; // type Request = <T>(req: T, timeout: number) => Promise<ResponseEntity<T>>
    type ReturnRequest = ReturnType<Request>; // type ReturnRequest = Promise<ResponseEntity<unknown>>
    

    这样搞了之后最终只能拿到返回类型 Promise<ResponseEntity<unknown>> ,对于 Promise 里面的泛型参数就无能为力了。这个时候就要用到 infer 关键字进行类型推断。

    所谓推断,就是你不用预先指定在泛型列表中,在运行时会自动判断,不过你得先预定义好整体的结构。举个例子

    type Foo<T> = T extends {t: infer Test} ? Test: string
    

    首选看 extends 后面的内容,{t: infer Test} 可以看成是一个包含 t 属性的类型定义,这个 t 属性的 value 类型通过 infer 进行推断后会赋值给 Test 类型,如果泛型实际参数符合 {t: infer Test} 的定义那么返回的就是 Test 类型,否则默认给缺省的 string 类型。

    举个例子加深下理解

    type One = Foo<number>  // string,因为number不是一个包含t的对象类型
    type Two = Foo<{t: boolean}>  // boolean,因为泛型参数匹配上了,使用了infer对应的type
    type Three = Foo<{a: number, t: () => void}> // () => void,泛型定义是参数的子集,同样适配
    

    使用 infer 实际上就是定义了一个泛型函数,通过传入类型参数,然后返回指定的类型。infer 用来对满足的泛型类型进行子类型的抽取,有很多高级的泛型工具也巧妙的使用了这个方法。

    回到最开始的问题,我们定义一个 infer 结构:

    type Awaited<T> = T extends Promise<infer U> ? U : T;
    

    然后给这个结构传入实际参数:

    type Res = Awaited<ReturnRequest>;
    /**
     * type Res = {
     *  success: boolean;
     *  result: unknown;
     *  message: string;
     * }
     */
    

    这样我们就拿到了 Promise 内部的泛型参数。

    再说一下 TS如何获取第三方库未导出的 Type 这篇文章中的问题,获取函数参数,我们使用了 Parameters<T> ,这样得到的是一个函数参数类型的元组,还得通过下标去拿对应的类型,感觉不是很优雅:

    type RadioProps = React.ComponentProps<typeof Radio.Group>;
    type ChangeEvent = RadioProps["onChange"];
    type ChangeFnParams = Parameters<NonNullable<ChangeEvent>>[0];
    

    看了一下 Parameters<T> 的定义:

    type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never
    

    可以看到 args 的类型是一个数组,因此推断出来的是一个参数类型的元组。我们希望在只有一个参数的时候直接拿到类型,因此改写了 Parameters<T> 的定义如下:

    type Param<T> = T extends (arg: infer U) => any ? U : never;
    

    这样就可以直接取到参数类型了:

    type RadioProps = React.ComponentProps<typeof Radio.Group>;
    type ChangeEvent = RadioProps["onChange"];
    type Param<T> = T extends (args: infer U) => any ? U : never;
    type ChangeFnParams = Param<NonNullable<ChangeEvent>>; // type ChangeFnParams = RadioChangeEvent
    

    顺便说一下 TS 内置的工具类型是怎么做的,ReturnType 是这样定义的:

    type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any
    

    Parameters 的定义如下:

    type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never
    

    ConstructorParameters 定义如下:

    type ConstructorParameters<T extends abstract new (...args: any) => any> = T extends abstract new (...args: infer P) => any ? P : never
    

    参考:

    TypeScript 高级用法

    展开全文
  • TypeScript `infer` 关键字

    千次阅读 2019-05-28 23:39:00
    考察如下类型: type PromiseType ...如果你对 TypeScript 不是那么陌生,可能知道官方类型库中提供了 ...infer ...infer ...infer ...转载于:https://www.cnblogs.com/Wayou/p/typescript_infer.html
  • 在 2.8 版本中,TypeScript 内置了一些与infer 有关的映射类型: 官网 Utility Types 文章目录infer用在函数中构造函数中内置类型ReturnType infer infer 最早出现在此 PR 中,表示在 extends 条件语句中待推断的...
  • 前言 TypeScript 是微软开发的一个开源的编程语言,通过在 JavaScript 的基础上添加静态类型定义构建而成。...本专栏用于记录和分享我在使用 TypeScript 中的感悟和心得,以下来介绍一下 infer 的用法: infer
  • Infer 关键字用于条件中的类型推导。Typescript 官网也拿 ReturnType 这一经典例子说明它的作用:typeReturnType<T>=Texten...
  • When working with conditionals types, within the “extends” expression, we can use the “infer” keyword to either get the type of the elements of an array, or even to get the return type of a ...
  • Customer, str: string) => string是两个参数, (parmas: infer P) => any 是一个参数 例子2:出现位置在参数上 注意infer的写法,inter获取返回值,它返回什么,是后面的三元表达式决定 type custFuncType = (cust...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 652
精华内容 260
关键字:

infertypescript