点击蓝色“脑洞前端”关注我哟

加个“星标”,带你揭开大前端的神秘面纱!

这是脑洞前端第「97」篇原创文章

2018 年底的时候,力扣发布了岗位招聘,其中就有前端,仓库地址:https://github.com/LeetCode-OpenSource/hire 。与大多数 JD 不同, 其提供了 5 道题, 并注明了: 完成一个或多个面试题,获取免第一轮面试的面试机会。完成的题目越多,质量越高,在面试中的加分更多。完成后的代码可以任意形式发送给 jobs@lingkou.com。以上几个问题完成一个或多个都有可能获得面试机会,具体情况取决于提交给我们的代码。

(力扣中国前端工程师 JD)

今天我们就来看下第二题:编写复杂的 TypeScript 类型。通过这道题来看下, TypeScript 究竟要到什么水平才能进力扣当前端?

其它四道题也蛮有意思的,值得一看。

问题描述

假设有一个叫 EffectModule 的类

class EffectModule {}

这个对象上的方法「只可能」有两种类型签名:

interface Action<T> {payload?: Ttype: string
}asyncMethod<T, U>(input: Promise<T>): Promise<Action<U>>syncMethod<T, U>(action: Action<T>): Action<U>

这个对象上还可能有一些任意的「非函数属性」

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 的函数,它接受 EffectModule 实例,将它变成另一个对象,这个对象上只有「EffectModule 的同名方法」,但是方法的类型签名被改变了:

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 定义如下:

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 之后:

type Connected = {delay(input: number): Action<string>;setMessage(action: Date): Action<number>;
};
const effectModule = new EffectModule();
const connected: Connected = connect(effectModule);

要求:

在 题目链接[1] 里面的 index.ts 文件中,有一个 type Connect = (module: EffectModule) => any,将 any 替换成题目的解答,让编译能够顺利通过,并且 index.tsconnected 的类型与:

type Connected = {delay(input: number): Action<string>;setMessage(action: Date): Action<number>;
};

「完全匹配」

以上是官方题目描述,下面我的补充

上文提到的index.ts 比 题目描述多了两个语句,它们分别是:

(题目额外信息)

思路

首先来解读下题目。题目要求我们补充类型 Connect 的定义, 也就是将 any 替换为不报错的其他代码。

回顾一下题目信息:

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

  • 这个对象上还可能有一些任意的「非函数属性」

  • 这个对象(EffectModule 实例)上的方法「只可能」有两种类型签名

根据以上信息,我们能够得到:我们只需要将作为参数传递进来的 EffectModule 实例上的函数类型签名修改一下,非函数属性去掉即可。所以,我们有两件问题要解决:

  1. 如何将非函数属性去掉

  2. 如何转换函数类型签名

如何将非函数属性去掉

我们需要定义一个泛型,功能是接受一个对象,如果对象的 value 是 函数,则保留,否则去掉即可。不懂泛型的朋友可以先看下我之前写的文章:你不知道的 TypeScript 泛型(万字长文,建议收藏)[2]

这让我想起了官方提供的 Omit 泛型 Omit<T,K>。举个例子:

interface Todo {title: string;description: string;completed: boolean;
}type TodoPreview = Omit<Todo, "description">;// description 属性没了
const todo: TodoPreview = {title: "Clean room",completed: false,
};

官方的 Omit 实现:

type Pick<T, K extends keyof T> = {[P in K]: T[P];
};
type Exclude<T, U> = T extends U ? never : T;
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

实际上我们要做的就是 Omit 的变种,不是 Omit 某些 key,而是 Omit 值为非函数的 key。

由于 Omit 非函数实际就就是 Pick 函数,并且无需显式指定 key,因此我们的泛型只接受一个参数即可。于是模仿官方的 Pick 写出了如下代码:

// 获取值为函数的 key,形如: 'funcKeyA' | 'funcKeyB'
type PickFuncKeys<T> = {[K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];// 获取值为函数的 key value 对,形如: { 'funcKeyA': ..., 'funKeyB': ...}
type PickFunc<T> = Pick<T, PickFuncKeys<T>>;

使用效果:

interface Todo {title: string;description: string;addTodo(): string;
}type AddTodo = PickFunc<Todo>;const todo: AddTodo = {addTodo() {return "关注脑洞前端~";},
};type ADDTodoKey = PickFuncKeys<Todo>; // 'addTodo'

可以看出,PickFunc 只提取了函数属性,忽略了非函数属性。

如何转换函数类型签名

我们再来回顾一下题目要求:

也就是我们需要知道「怎么才能提取 Promise 和 Action 泛型中的值」

实际上这两个几乎一样,会了一个,另外一个也就会了。我们先来看下 Promise

从:

(arg: Promise<T>) => Promise<U>

变为:

(arg: T) => U;

如果想要完成这个需求,需要借助infer。只需要在类型前加一个关键字前缀 infer,TS 会将推导出的类型自动填充进去。

infer 最早出现在此 官方 PR 中,表示在 extends 条件语句中待推断的类型变量。

简单示例如下:

type ParamType<T> = T extends (param: infer P) => any ? P : T;

在这个条件语句 T extends (param: infer P) => any ? P : T 中,infer P 表示待推断的函数参数。

整句表示为:如果 T 能赋值给 (param: infer P) => any,则结果是 (param: infer P) => any 类型中的参数 P,否则返回为 T。

一个更具体的例子:

interface User {name: string;age: number;
}type Func = (user: User) => void;type Param = ParamType<Func>; // Param = User
type AA = ParamType<string>; // string

这些知识已经够我们用了。更多用法可以参考 深入理解 TypeScript - infer[3]

根据上面的知识,不难写出如下代码:

type ExtractPromise<P> = {[K in PickFuncKeys<P>]: P[K] extends (arg: Promise<infer T>) => Promise<infer U>? (arg: T) => U: never;
};

提取 Action 的 代码也是类似:

type ExtractAction<P> = {[K in keyof PickFunc<P>]: P[K] extends (arg: Action<infer T>) => Action<infer U>? (arg: T) => Action<U>: never;
};

至此我们已经解决了全部两个问题,完整代码见下方代码区。

关键点

  • 泛型

  • extends 做类型约束

  • infer 做类型提取

  • 内置基本范型的使用和实现

代码

我们将这几个点串起来,不难写出如下最终代码:

type ExtractContainer<P> =  {[K in PickFuncKeys<P>]:P[K] extends (arg: Promise<infer T>) => Promise<infer U> ? (arg: T) => U :P[K] extends (arg: Action<infer T>) => Action<infer U> ? (arg: T) => Action<U> :never
type Connect = (module: EffectModule) => ExtractContainer<EffectModule>

完整代码在我的 Gist[4] 上。

总结

我们先对问题进行定义,然后分解问题为:1. 如何将非函数属性去掉, 2. 如何转换函数类型签名。最后从分解的问题,以及基础泛型工具入手,联系到可能用到的语法。

这个题目不算难,最多只是中等。但是你可能也看出来了,其不仅仅是考一个语法和 API 而已,而是考综合实力。这点在其他四道题体现地尤为明显。这种考察方式能真正考察一个人的综合实力,背题是背不来的。我个人在面试别人的时候也非常喜欢问这种问题。

只有「掌握基础 + 解决问题的思维方法」,面对复杂问题才能从容不迫,手到擒来。

Reference

[1]

题目链接: https://codesandbox.io/s/4tmtp

[2]

你不知道的 TypeScript 泛型(万字长文,建议收藏): https://lucifer.ren/blog/2020/06/16/ts-generics/

[3]

深入理解 TypeScript - infer: https://jkchao.github.io/typescript-book-chinese/tips/infer.html#%E4%BB%8B%E7%BB%8D

[4]

Gist  地址: https://gist.github.com/azl397985856/5aecb2e221dc1b9b15af34680acb6ccf

[5]

Lucifer - 知乎: https://www.zhihu.com/people/lu-xiao-13-70

推荐阅读

1、你不知道的前端异常处理(万字长文,建议收藏)

2、你不知道的 TypeScript 泛型(万字长文,建议收藏)

3、你不知道的 Web Workers (万字长文,建议收藏)

4、lucifer与它的《力扣加加》来啦

5、或许是一本可以彻底改变你刷 LeetCode 效率的题解书

6、一文助你搞懂 AST

7、如果张东升是个程序员

关注加加,星标加加~

如果觉得文章不错,帮忙点个在看呗

想去力扣当前端,TypeScript 需要掌握到什么程度?相关推荐

  1. 1024,给大家谈谈如何搞透刷题 玩转力扣!

    前言 大家好,我是bigsai,好久不见!今天1024,祝大家程序员节快乐,写代码永没bug!今天就给各位小伙伴分享我自己刷题力扣的一些小方法,不一定很有用但是可以参考,祝你更高效的变强! 最近在一些 ...

  2. 最大子序列和问题c语言力扣,力扣

    力扣刷题-第1题:两数之和2021-05-24 22:03:30 目录 一.写在最前面 二.题目信息 解题思路 一.写在最前面 致愿意重新开始努力的自己: 希望你能够持续的坚持,不半途而废. 二.题目 ...

  3. 【关于为什么要刷力扣的思考】记第二次周赛AK

    前言 从上次AK周赛的一月底,磕磕绊绊到五月初,总共经历了20多场的周赛 在这20场周赛中,四题:三题:两题:一题 = 2:12:8:1 总体来说应该还是在两到三题中间徘徊 但很多时候做出的两题,并非 ...

  4. 数据结构与算法笔记:哈希表——力扣389

    原题: 给定两个字符串 s 和 t ,它们只包含小写字母.字符串 t 由字符串 s 随机重排,然后在随机位置添加一个字母.请找出在 t 中被添加的字母. 思路: 首先咱们抛开编程知识,就当它是咱们日常 ...

  5. ❤️导图整理数组4: 三数之和 相比于 两数之和 的难点, 力扣15❤️

    此专栏文章是对力扣上算法题目各种方法的总结和归纳, 整理出最重要的思路和知识重点并以思维导图形式呈现, 当然也会加上我对导图的详解. 目的是为了更方便快捷的记忆和回忆算法重点(不用每次都重复看题解), ...

  6. 力扣第 107 场双周赛

    因为打abc打(。・∀・)ノ゙嗨了,忘记cf是10.05了,所以只能去力扣了 A.直接模拟,然后把用过的点记录下次别用就行 class Solution:def maximumNumberOfStri ...

  7. ❤️思维导图整理大厂面试高频数组19: 股票问题III的dp数组构建/初始化和空间优化难点, 力扣123❤️

    此专栏文章是对力扣上算法题目各种方法的总结和归纳, 整理出最重要的思路和知识重点并以思维导图形式呈现, 当然也会加上我对导图的详解. 目的是为了更方便快捷的记忆和回忆算法重点(不用每次都重复看题解), ...

  8. ❤️导图整理数组5: 四数之和 相比 三数之和 的大量优化, 力扣18详细注解❤️

    此专栏文章是对力扣上算法题目各种方法的总结和归纳, 整理出最重要的思路和知识重点并以思维导图形式呈现, 当然也会加上我对导图的详解. 目的是为了更方便快捷的记忆和回忆算法重点(不用每次都重复看题解), ...

  9. ❤️导图整理数组3: 两数之和II有序数组, 多个有序, 思路全变, 力扣167❤️

    此专栏文章是对力扣上算法题目各种方法的总结和归纳, 整理出最重要的思路和知识重点并以思维导图形式呈现, 当然也会加上我对导图的详解. 目的是为了更方便快捷的记忆和回忆算法重点(不用每次都重复看题解), ...

最新文章

  1. mysql sa密码是什么_忘记mysql数据库root密码
  2. boost::hana::erase_key用法的测试程序
  3. softmax实现cifar10分类
  4. 信捷伺服刚性调整_信捷電氣(603416):伺服與PLC增長將加速,口罩機解決方案帶來新增量...
  5. 动态规划——基本思想
  6. 前端基础:JavaScript 代码风格指南
  7. hexo -d 部署的时候报错 FATAL Something's wrong Template render error: expected variable
  8. 批处理实现—循环Ping指定网段(检测网络时使用)
  9. 如何配置数据库ODBC数据源
  10. awakeFromNib 与 viewDIdLoad 自己小结
  11. rocketMQ启动
  12. Python中的if __name__ == ‘__main__‘什么意思?
  13. 亚马逊产品违反受限政策,亚马逊受限产品恢复在售
  14. 软件测试职业发展规划
  15. C语言 写一个函数,输入一个4位数字,要求输出这4个数字字符,但每两个数字之间空一个空格。如输入1990,应该输出“1 9 9 0”
  16. 考英语二的计算机学硕,考研201英语是英语一还是英语二?
  17. 链券——区块链世界下的卡券
  18. speech api_如何使用Web Speech API构建文本语音转换应用
  19. 2022年山东省安全员C证考试试题模拟考试平台操作
  20. 达内java ppt下载,达内教程.ppt

热门文章

  1. cartographer_pose_extrapolator
  2. celery 停止任务_celery 停止执行中 task
  3. 如何在library中使用productFlavors
  4. 2021年数据库课设该怎么做?一个超市管理系统,简单的前后端分离项目,带你从概要设计走到项目发布!(Vue.js+SpringBoot+MybatisPlus)
  5. Vulnhub--bulldog
  6. “看到他的汇报,我彻底服了!”:这个工具能让你的图表多炫酷?
  7. PowerTool kEvP.sys=V4.2 内核拒绝服务漏洞
  8. 解决中文名单按拼音排序的问题
  9. 解决jmeter5.4.3在高分辨率下的显示问题
  10. 京东大客户开放平台VOP接口对接记录