键入TypeScript泛型的重复模式

时间:2019-08-01 20:22:42

标签: typescript

我想为将有效键与.附加在一起的泛型函数采用无限个参数。从技术上讲,它可以使用与嵌套对象一样多的参数。

以下代码段按原样工作,但是我想知道它是否可以接受任意数量的参数,而不是仅手动输入的参数?

const tt = <Lang>() => {
  return <
    A extends keyof Lang,
    B extends keyof Omit<Lang[A], keyof string>,
    C extends keyof Omit<Lang[A][B], keyof string>,
    D extends keyof Omit<Lang[A][B][C], keyof string>,
    E extends keyof Omit<Lang[A][B][C][D], keyof string>,
    F extends keyof Omit<Lang[A][B][C][D][E], keyof string>
    // Can this repeat without manually typing each out?
  >(
    a: A,
    b?: B,
    c?: C,
    d?: D,
    e?: E,
    f?: F,
  ) => {
    return [a, b, c, d, e, f].filter(Boolean).join('.');
  };
};

console.log(tt<{Parent: {child: 'child'}}>()('Parent', 'child'));
// Console: "Parent.child"

代码按原样运行,但是应该可以接受无限量的参数。

也许是这样的:

const tt = <Lang>() => {
  return <A extends SomethingGoesHere>(...args: A) => {
    return args.join('.');
  };
};

console.log(tt<{Parent: {child: 'child'}}>()('Parent', 'child'));
// Console: "Parent.child"

一个后续问题:我讨厌这必须是一个高阶函数才能使通用类型起作用。为什么打字稿不允许我根据提供的通用类型A来推断F-Lang

在理想的世界中,我可以做到这一点:

const tt = <
  Lang,
  A extends keyof Lang,
  B extends keyof Omit<Lang[A], keyof string>,
  C extends keyof Omit<Lang[A][B], keyof string>,
  D extends keyof Omit<Lang[A][B][C], keyof string>,
  E extends keyof Omit<Lang[A][B][C][D], keyof string>,
  F extends keyof Omit<Lang[A][B][C][D][E], keyof string>
>(
  a: A,
  b?: B,
  c?: C,
  d?: D,
  e?: E,
  f?: F,
) => {
  return [a, b, c, d, e, f].filter(Boolean).join('.');
};

tt<{Parent: {child: 'child'}}>('Parent', 'child');
// ^ error: Expected 7 type arguments, but got 1.ts(2558)

但是它会抛出该错误。

1 个答案:

答案 0 :(得分:1)

@ TitianCernicova-Dragomir是正确的,这里没有什么好(至少没有supported)。通常比使用TypeScript的类型系统更好的一件事是将代码重构为更好理解的内容。

例如,如果您不支持可变参数函数,而是创建一个可链接函数,其中每个调用将当前类型T缩小为其属性T[K]之一,该怎么办?像这样:

type ObjStrKey<T> = T extends object ? Extract<keyof T, string> : never;

interface Keychain<T> {
  <K extends ObjStrKey<T>>(k: K): Keychain<T[K]>;
  value: string;
}

function keychain<T>(): Keychain<T> {
  function kc<T, K extends ObjStrKey<T>>(
    this: Keychain<T>,
    k: K
  ): Keychain<T[K]> {
    const value = (this.value ? this.value + "." : "") + k;
    const v = { value };
    return Object.assign(kc.bind(v), v);
  }
  const v = { value: "" };
  return Object.assign(kc.bind(v), v);
}

这是这样的:

interface Person {
  parent: Person;
  child: Person;
  spouse: Person;
  name: string;
}

const kcp = keychain<Person>();
console.log(kcp("child")("spouse")("child")("parent")("name").value);
// child.spouse.child.parent.name

Link to code

对于编译器而言,这很容易,因为每个函数调用仅执行一次属性查找。


如果您发现对thisbind感到有些奇怪,可以将其更改为常规的class并将函数调用更改为方法调用,如下所示:

type ObjStrKey<T> = T extends object ? Extract<keyof T, string> : never;

class Keychain<T> {
  static make<T>() {
    return new Keychain<T>("");
  }
  private constructor(public value: string) {}
  k<K extends ObjStrKey<T>>(k: K): Keychain<T[K]> {
    const value = (this.value ? this.value + "." : "") + k;
    return new Keychain<T[K]>(value);
  }
}

有些不同:

const kcp = Keychain.make<Person>();
console.log(kcp.k("child").k("spouse").k("child").k("parent").k("name").value);
// child.spouse.child.parent.name

Link to code


无论如何,只是一个想法。希望能帮助到你;祝你好运!