前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >再次研究一道网红typescript面试题

再次研究一道网红typescript面试题

作者头像
lhyt
发布2020-07-15 15:59:26
1.2K0
发布2020-07-15 15:59:26
举报
文章被收录于专栏:lhyt前端之路lhyt前端之路

题目描述

原题地址 有一个叫EffectModule的类

代码语言:javascript
复制
class EffectModule {}


复制代码

这个对象上有各种各样的属性,string、number、function等,其中他的function类型的属性只可能有两种类型签名:

代码语言:javascript
复制
asyncMethod<T, U>(input: Promise<T>): Promise<Action<U>>
syncMethod<T, U>(action: Action<T>): Action<U>
复制代码

现在有一个叫 connect 的函数,它接受 EffectModule实例,将它变成另一个对象,这个对象上只有EffectModule 的同名方法,但是方法的类型签名被改变了:

代码语言:javascript
复制
interface Action<T> {
  payload?: T
  type: string
}

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>

复制代码

例子: EffectModule 定义如下:

代码语言:javascript
复制
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之后:

代码语言:javascript
复制
// 问题就是把any换成解答,使得ts编译正常
// 也就是让类型Connect的返回值和Connected完全一样,ts编译通过
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>
}
const effectModule = new EffectModule()
const connected: Connected = connect(effectModule)
复制代码

要求:type Connect = (module: EffectModule) => any,把any换成解答,让类型Connect的返回值和Connected完全一样,使得ts编译正常

很明显,我们需要做的事情就是:

  • 把EffectModule的函数类型取出来
  • 把函数的参数、返回值解promise/action

把EffectModule的函数类型取出来

说到取某些key出来,就是Pick或者Omit了。但ts又没有类似Object.keys().filter这种方式,需要使用映射类型+never去做特殊处理。整个流程就是:映射类型 =》 如果值为函数类型,返回key,否则返回never =》 对映射类型取值,得到函数类型的key

映射类型

是指把一个类型映射为另一个类型,key用的是类似for in的语法,表示遍历旧的类型里面每一个key:

代码语言:javascript
复制
type mapType0<T> = {
  [k in keyof T]: T[k]
}
复制代码

在映射类型后面加上[keyof T],相当于valueof的方法了——返回的是这个类型里面所有的key的value的联合类型:

代码语言:javascript
复制
const o = {
    a: 1,
    b: '2'
}

type map1 = mapType0<typeof o>[keyof typeof o]
// string | number
复制代码

Pick和Omit

我们知道了映射类型,基于此可以实现一个Pick(ts其实已经自带)

代码语言:javascript
复制
type myPick<T, K extends keyof T> = {
  [k in K]: T[k]
}
复制代码

第二个泛型参数约束key来源于T,这样子就可以确保取得到原对象某些key了。基于Pick,就可以实现Omit

代码语言:javascript
复制
type MyOmit<T, K extends keyof any> 
= Pick<T, Exclude<keyof T, K>>;
复制代码

获取value为function类型的key

代码语言:javascript
复制
type FunctionKeys<T> = {
  [k in keyof T]: T[k] extends Function ? k : never
}[keyof T]
type functionKeys = Pick<EffectModule, FunctionKeys<EffectModule>>
复制代码

使用Omit实现

代码语言:javascript
复制
type noFunctionKeys<T> = {
  [k in keyof T]: T[k] extends Function ? never : k
}[keyof T]
type functionKeys = Omit<EffectModule, noFunctionKeys<EffectModule>>
复制代码

现在,你得到了一个对象,只有value为函数类型的key了

把函数的参数、返回值解promise/action

infer

infer表示在condition type的条件语句中待推断的类型变量,可以理解为解方程,infer x表示x是待求解的变量,infer相当于一个标记作用。例如returntype就是靠infer实现的

代码语言:javascript
复制
type MyReturnType<T> = T extends (...args: any[]) => infer P ? P : any;
复制代码

再看两个例子,比如解Promise、获取数组的item类型:

代码语言:javascript
复制
type UnPromisify<T> = T extends Promise<infer U> ? U : T
type UnArray<T> = T extends (infer U)[] ? U : T
复制代码

实现效果

所以基于前面,我们可以把函数参数、返回值提取出来,解Promise、Action

代码语言:javascript
复制
type UnPromisify<T> = T extends Promise<infer U> ? U : T
type UnAction<T> = T extends Action<infer U> ? U : T

type MapTypeToUnPromisifyAndUnAction<T extends any[]> = {
  [k in keyof T]: UnAction<UnPromisify<T[k]>>
}

type Connect = (module: EffectModule) => ({
  [functionKey in keyof functionKeys]: (input: MapTypeToUnPromisifyAndUnAction<
    Parameters<functionKeys[functionKey]>
    >[number]) => UnPromisify<ReturnType<functionKeys[functionKey]>>
})

复制代码

全部代码

代码语言:javascript
复制
type FunctionKeys<T> = { [k in keyof T]: T[k] extends Function ? k : never }[keyof T]
type functionKeys = Pick<EffectModule, FunctionKeys<EffectModule>>

type UnPromisify<T> = T extends Promise<infer U> ? U : T
type UnAction<T> = T extends Action<infer U> ? U : T

type MapTypeToUnPromisifyAndUnAction<T extends any[]> = {
  [k in keyof T]: UnAction<UnPromisify<T[k]>>
}

type Connect = (module: EffectModule) => ({
  [functionKey in keyof functionKeys]: (input: MapTypeToUnPromisifyAndUnAction<
    Parameters<functionKeys[functionKey]>
    >[number]) => UnPromisify<ReturnType<functionKeys[functionKey]>>
})

复制代码
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020年07月13日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 题目描述
  • 把EffectModule的函数类型取出来
    • 映射类型
      • Pick和Omit
        • 获取value为function类型的key
        • 把函数的参数、返回值解promise/action
          • infer
            • 实现效果
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档