我是靠谱客的博主 如意猫咪,最近开发中收集的这篇文章主要介绍一道TypeScript题目,快速上手TS(keyof,extends,typeof),觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

一道TypeScript题目,快速上手TS(keyof,extends,typeof)

本人为ts初学者,本文题目和答案均来自于typescript高级用法之infer的理解与使用
由于原文并没有对语法部分进行详细说明,在理解后写下自己的步骤
建议阅读前先阅读下typescript高级用法之infer的理解与使用,有益于帮助理解

题目

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: (action: 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());

题目所求的是 获取type Connect = (module: EffectModule) => any,把any变成预期的类型.

  1. 首先提取EffectModule的方法(注意:只是提取class里方法Function的键名)
    //首先展示如何获取function键名
    type MethodName<T> = {[F in keyof T]:T[F] extends Function ? F :never}[keyof T]
    type EE =
    MethodName<EffectModule>
    // 此时EE的类型为
    type EE = "delay" | "setMessage";
    
    1. 逐步解释,整体相当于
      type MethodName<Obj> = Obj[keyof Obj]; //用Obj的键获取T的键值
      
    2. 再继续看[F in keyof T]:T[F],这里是一个遍历语法
      //去掉其他代码单纯看这段
      type MethodName只剩遍历版<T> = {[F in keyof T]:T[F]};
      //1. [F in keyof T] 首先遍历T里的键,每一项为F
      //2. [F in keyof T]:T[F] 每一项F的值为T里去F键的键值
      // 相当于 let obj = {a:1='a的键值',b:'b的键值'}; obj[a] = 1;
      //套入到EffectModule
      type EE =
      MethodName只剩遍历版<EffectModule>;
      //此时EE的类型为
      type EE = {
      count: number;
      message: string;
      delay: (input: Promise<number>) => Promise<{
      payload: string;
      type: string;
      }>;
      setMessage: (action: Action<Date>) => {
      payload: number;
      type: string;
      };
      }
      //可见 "MethodName只剩遍历版" 只是把EffectModule的类型遍历了一次,和EffectModule本身相同
      
    3. 接上后面的三元表达式就开始实现过滤了{[F in keyof T]:T[F] extends Function ? F :never}
      // 由于我们已知{[F in keyof T]:T[F]}得到的类型EE为
      type EE = {
      count: number;
      message: string;
      delay: (input: Promise<number>) => Promise<{
      payload: string;
      type: string;
      }>;
      setMessage: (action: Action<Date>) => {
      payload: number;
      type: string;
      };
      }
      //则{[F in keyof T]:T[F] extends Function ? F :never},在此时可以理解为
      //{[F in keyof EE]:EE[F] extends Function ? F :never} 
      //关键是EE[F] extends Function ? F :never,解释一下就是 EE里取键值F的类型
      //EE[F]的类型是否继承于Function?是,就把EE[F]的类型赋值为F(也就是键名),否则就是never
      //为什么是never,后面在[keyof T]处再说明
      //未完成版是因为有[keyof T]才完成过滤
      type MethodName过滤未完成版<T> = {[F in keyof T]:T[F] extends Function ? F :never};
      //套入EffectModule
      type EE =
      MethodName过滤未完成版<EffectModule>;
      //EE的类型为
      type EE = {
      //此时,除去不是function的键名,其他键名和键值一一对应
      count: never;
      message: never;
      delay: "delay";
      setMessage: "setMessage";
      }
      
    4. 接下来只剩下最后的[keyof T]了,就像第一点所说,这个"[ ]"其实就是取前面对象的键值,类似Obj[key]
      //因为此时已知 MethodName过滤未完成版<EffectModule>的值EE
      type EE = {
      //此时,除去不是function的键名,其他键名和键值一一对应
      count: never;
      message: never;
      delay: "delay";
      setMessage: "setMessage";
      }
      //所以[keyof T]套入回EE,就相当于
      type MethodName过滤完整版<EffectModule> = {
      count: never;
      message: never;
      delay: "delay";
      setMessage: "setMessage";
      }['delay'|'setMessage' /*keyof EffectModule的值*/]; //这里原本是[keyof T],此时T是代入EffectModule
      //所以得到结果
      type EE = "delay" | "setMessage";
      //这里有两个很关键的知识点
      //1.keyof会返回数据类型的联合类型
      //2.keyof在遇到值是never的时候不会返回
      //到此,我们已经获取了这个class的两个function的键名
      
  2. 此时再回头看题目修改 Connect 的类型,让 connected 的类型变成预期的类型
    1. 首先看看connected的期望类型
      type Connect = (module: EffectModule) => any;
      const connect: Connect = m => ({
      delay: (input: number) => ({
      type: 'delay',
      payload: `hello 2`
      }),
      setMessage: (action: Date) => ({
      type: "set-message",
      payload: input.getMilliseconds()
      })
      });
      //可以很明显的看出Connect期望返回类型(仅方法部分)
      type Connected = {
      delay:(input: number) => Action<string>;
      setMessage:(action: Date) => Action<number>;
      }
      //而题目EffectModule类型(仅方法部分)
      type EffectModule = {
      delay:(input: Promise<T>) => Promise<Action<U>>;
      setMessage:(action: Action<T>) => Action<U>
      }
      //题目就是需要把Connect的返回值any推断成Connected
      //但实际我们只需要关注Connected里面两个函数
      //我们可以直接写出EffectModule和Connected里函数泛型函数签名
      //delay
      type asyncMethod<T, U> = (input: Promise<T>) => Promise<Action<U>>;
      type asyncMethodConnect<T, U> = (input: T) => Action<U>;
      //setMessage
      type syncMethod<T, U> = (action: Action<T>) => Action<U>;
      type syncMethodConnect<T, U> = (action: T) => Action<U>;
      //至于为什么是async,sync这种命名,因为题目里就是这么命名的
      
  3. 现在我们已经有了了function的键名,函数原类型,需要被推导类型的泛型,这里就可以利用keyof的循环做一个分发,实现的关键是infer
    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的用法,这里就不展开了
    //这里已经就已经得到答案了
    type Connected = (module: EffectModule) => {
    [F in MethodName<typeof module>]:EffectMethodAssign<typeof module[F]>;
    }
    //其实看到这里,我相信都是能看懂这段代码的
    // [F in MethodName<typeof module>]负责取键值
    // EffectMethodAssign<typeof module[F]> 负责分发类型
    

最后

以上就是如意猫咪为你收集整理的一道TypeScript题目,快速上手TS(keyof,extends,typeof)的全部内容,希望文章能够帮你解决一道TypeScript题目,快速上手TS(keyof,extends,typeof)所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(28)

评论列表共有 0 条评论

立即
投稿
返回
顶部