美文网首页
一个典型的TypeScript 题目

一个典型的TypeScript 题目

作者: QLing09 | 来源:发表于2020-08-10 14:52 被阅读0次

TypeScript 题

在读深入理解typescript读到infer这张遇到的这个题,最后参考当前文章

我们看完这些,我们发现typescript可以减少90%的一些拼写、字段漏写、传参不匹配等基本错误。还可以增减代码的健壮性,代码的可维护性提升。

这个题目是一个很有代表意义的题,在实际工作中也会遇到类似的问题。

问题定义

假设有一个叫EffectModule 的类

假设有一个叫 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的同名方法,但是方法的类型签名被改变了:

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)

要求

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

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

完全匹配。

解答

  • 挑选函数
  • 取待推断的变量类型
    补充
type FuncName<T> = { [P in keyof T]: T[P] extends Function ? P : never }[keyof T];

详细

题目要求是把 type Connect 中的 any 替换掉,并符合:

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

并编译通过。
再把问题简化一下,就是设计一个工具类型,让题目中的EffectModule 的实例转化为符合要求的Connected

即:

type Connect = (module: EffectModule) => xxx ---> type Connected = {
  delay(input: number): Action<string>;
  setMessage(action: Date): Action<number>;
}

仔细观察上面的伪代码实例,Connected其实是一个对象类型,其中包含的 key-value 就是EffectModule 中的方法转化而来的,所以我们的入手处就是想办法将EffectModule 中的方法转化为对应的Connected 中的 key-value

type Connect = (module: EffectModule) => {
  ...
}

再观察 Connected 的属性与 EffectModule 的方法是不是有共同之处?他们的名字是一样的,所以我们得先设计一个工具类型把 EffectModule 中的方法名取出来。
EffectModule,包含非方法的「属性」,所以得做个判断,如果是属性值类型是函数那么取出,否则不要取出

type FuncName<T> = { [P in keyof T]: T[P] extends Function ? P : never }[keyof T];

方法的类型签名被改变了:

asyncMethod<T, U> = (input: Promise<T>) => Promise<Action<U>>  变成了
asyncMethodConnect<T, U> = (input: T) => Action<U> 

syncMethod<T, U> = (action: Action<T>) => Action<U>  变成了
syncMethodConnect<T, U> = (action: T) => Action<U>

着手转化工作

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

目前我们有两个主要的工具类型EffectModuleMethodsConnect 负责类型的转化,methodsPick负责取出方法名,现在我们先把方法名取出:

type EffectModuleMethods = FuncName<EffectModule>

最后

type Connect = (module: EffectModule) => {
  [M in EffectModuleMethods]: EffectModuleMethodsConnect<EffectModule[M]>
}
// import { expect } from "chai";

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"
    };
  }
}

type MethodPick<T> = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T];

type asyncMethod<T, U> = (input: Promise<T>) => Promise<Action<U>>;
type asyncMethodConnect<T, U> = (input: T) => Action<U>;
type syncMethod<T, U> = (input: Action<T>) => Action<U>;
type syncMethodConnect<T, U> = (input: T) => Action<U>;
type EffectModuleMethodsConnect<T> = T extends asyncMethod<infer U, infer V>
  ? asyncMethodConnect<U, V>
  : T extends syncMethod<infer U, infer V>
  ? syncMethodConnect<U, V>
  : never;

  type EffectModuleMethods = MethodPick<EffectModule>;

// 修改 Connect 的类型,让 connected 的类型变成预期的类型
type Connect = (module: EffectModule) => {
  [M in EffectModuleMethods]: EffectModuleMethodsConnect<EffectModule[M]>
};

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

相关文章

网友评论

      本文标题:一个典型的TypeScript 题目

      本文链接:https://www.haomeiwen.com/subject/kyfjdktx.html