力扣的 TypeScript 面试题

前言

几个礼拜前我在 GitHub 上面看到了力扣的面试题仓库,其中有一道 TypeScript 题目,要求编写复杂类型定义。当时我对类型只是有一个泛泛的了解,所以这题目是看得一头雾水。最近稍微看了一下 TypeScript 的文档,尤其是这两天看了两个“高级”的用法,所以决定重新尝试一下这道题。题目原地址在这里,下面是原题描述。

题目描述

假设有一个叫 EffectModule 的类

class EffectModule {}

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

interface Action<T> {
  payload?: T
  type: 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 的同名方法,但是方法的类型签名被改变了:

例子:

EffectModule 定义如下:

connect 之后:

要求: 将下面代码中的 any 替换成题目的解答,让编译能够顺利通过。

分析题目

我们来梳理一下题目,可以大概理解为有一个 EffectModule 类型,它有两种函数签名,还可以有任意的非函数属性,如下定义

另外有一个 Connected 类型,它只有 EffectModule 上的同名方法,但是方法的类型签名变了,而且它没有非函数属性

Connect 是一个函数签名,它接收一个 EffectModule 类型的对象,返回一个 Connected 类型的对象。

我们要做的就是把 any 改成符合要求的类型定义。

总结一下,我们有两件事情要做:

  1. Connect 参数对象上的非函数属性去掉

  2. Connect 参数对象上的函数签名改成符合题目要求

开始解题

去除非函数属性

去除非函数属性,也就是挑出函数属性

首先想一下,如果我们要基于原有类型创建新类型,要怎么做?比如

上面的代码只是定义了一个和原有类型一模一样的新类型,其中 keyof T 相当于遍历了类型 T 的所有属性。

如果我们把 keyof T 部分替换成只包含函数属性的集合,那不就完了吗?我们来改一下上面的代码

可惜 TypeScript 并没有提供一个 PickFuncKey 方法,所以我们来自己实现一个。

要求: PickFuncKey 接收一个类型参数 T,返回 T 中所有函数属性的 key 的集合。注意我们只需要 key

这里有一个关键知识点就是:如何判断属性是否函数属性?

答案就是 extends 关键字。

PickFuncKey<T> 可以定义如下

改变函数签名

第二步是改变方法的类型签名,我们的对象只有两种函数签名。如果是

就改成

如果是

就改成

当然首先我们得判断函数属性具体是哪种签名,这又用到了 extends

p.s. 这里我把 MakeConnected 的形参改成了 M,便于区分。

首先用

来判断是不是 asyncMethod 的签名,是就返回处理后的函数签名(TODO),不是的话就继续用

来判断符不符合 syncMethod 的函数签名,是就返回处理后的函数签名(TODO),不是就返回 never

接着就是如何修改函数签名了,首先第一个

要改成

需求简单明确,但问题是,TU 都从哪里来?我们的 MakeConnected 只接收一个参数 M,而且,在每个函数属性的签名中, TU 都是不一样的。

这时候 infer 关键字就派上用场了。

如果你不知道 infer 是干什么的,我这里有一篇小笔记,或者去查下文档吧。

有了 infer 之后我们就可以这样修改函数签名了

第二种函数签名同理

完整代码

后记

其实这样子分析完之后发现,这道题目也没有特别复杂嘛,主要还是自己学得太少。

完整的代码在这里

Last updated

Was this helpful?