【问题标题】:Remove a constructor signature (or a call signature) from a generic type从泛型类型中删除构造函数签名(或调用签名)
【发布时间】:2020-10-10 16:33:23
【问题描述】:

问题

拥有一个具有构造函数签名的泛型类型T(例如,此类型:{ new(): any }),返回一个与T 相同的类型U,只是它缺少构造函数签名。将构造函数签名添加回U 应该会导致两种类型相同。

我为什么需要这个

假设我正在编写一个函数,该函数接受一个类并将其 构造函数签名 更改为不同的东西。这通常是代理的情况,例如,此代理从构造函数调用中删除最后一个参数并记录它:

function trapClass(cl) {
    return new Proxy(cl, {
        construct: (target, args) => {
            console.log( args.pop() )
            return new target(...args)
        }
    })
}

class Foo { constructor(...args){ console.log(args) } }
const Bar = trapClass(Foo)
new Foo(1, 2, 3) // [1, 2, 3]
new Bar(1, 2, 3) // 3, [1, 2]

如何输入?函数trapClass<T>(cl: T) 应该是一个通用函数,它接受带有构造函数签名的T,并返回带有不同构造函数签名的T。当我尝试将T 与新签名相交时,例如:T & { new(a, b, c): any },那么新签名不会替换旧签名,而是与旧签名合并为重载调用。

如果我知道如何从T 中删除构造函数签名,那么问题将是微不足道的。

调用签名

虽然我不需要从T 中省略调用签名,但删除构造函数签名和调用签名的问题非常相似。我希望这两个问题的解决方案只会在new 关键字和Parameters 而不是ConstructorParameters 方面有所不同。所以我认为答案可能会提到这两个问题。但是,如果解决方案不太相似,请不要打扰调用签名,我会提出一个单独的问题:)

【问题讨论】:

  • 所需的解决方案应该适用于 any 类还是只适用于 Foo?

标签: typescript typescript-typings


【解决方案1】:

TypeScript 3.x

你可以试试这个:

type Constructor<T = any> = new (...args: any[]) => T;

function trapClass<T extends Constructor>(cl: T): Pick<T, keyof T> & { new (a: number, b: number): T } {
  return new Proxy(cl, {
      construct: (target, args) => {
          console.log( args.pop() )
          return new target(...args)
      }
  })
}

class Foo { constructor(...args: any[]){ console.log(args) } }
const Bar = trapClass(Foo);

new Foo(1, 2, 3) // [1, 2, 3]
new Bar(1, 2, 3) // 3, [1, 2]

这适用于Foo。如果您希望 trapClass 与 any 类一起使用,则解决方案会更复杂 - 您必须考虑不接受任何参数的类,以及包含数量的类构造函数的参数是可变参数。

注意Pick&lt;T, keyof T&gt; 部分。当T 是构造函数时,生成的类型将是包含该构造函数的静态属性的对象。对于构造函数Foo,它将是{ prototype: Foo }。如果将静态属性(或方法)添加到 Foo,它也会出现在该对象中。实际上,它将从Foo 中删除构造签名。

class Foo {
    constructor(...args: [number, number, number]) {
        console.log(args)
    }

    static defaults = {
        foo: 'bar'
    }
};

const Bar = trapClass(Foo);

Bar.defaults.foo; // string

旁注:这与使用常见的Overwrite 助手完全相同。

type Overwrite<T, U> = Pick<T, Exclude<keyof T, keyof U>> & U;

function trapClass<T extends Constructor>(cl: T): Overwrite<T, { new (...args: [number, number]): T }> {
  return new Proxy(cl, {
      construct: (target, args) => {
          console.log( args.pop() )
          return new target(...args)
      }
  })
}

TypeScript 4

TypeScript 4 包括对 variadic tuple types 的支持。这允许您从原始构造函数类型中动态删除最后一个参数。

type DropLast<T extends readonly unknown[]> = T extends readonly [...infer U, any] ? U : [...T];

type Constructor<T extends readonly unknown[], U> = new (...args: T) => U;

function trapClass<T extends Constructor<any, any>>(cl: T): Constructor<DropLast<ConstructorParameters<T>>, T> {
  return new Proxy(cl, {
      construct: (target, args) => {
          console.log( args.pop() )
          return new target(...args)
      }
  })
}

class Foo {
    constructor(...args: [number, number, number]) {
        console.log(args)
    }
}
const Bar = trapClass(Foo);

new Foo(1, 2, 3) // [1, 2, 3]
new Bar(1, 2, 3) // Compile-time error (expected 2 args)

Playground

【讨论】:

  • 谢谢,这看起来正是我需要的!请您解释一下这是如何工作的? Pick&lt;T, keyof T&gt; 是否创建了一个完全是 T 但没有调用和构造函数签名的类型?问题的要点是“如何删除构造函数签名”,所以如果这是真的,那么真正的答案就像type U&lt;T&gt; = Pick&lt;T, keyof T&gt; 一样简单! ?️
  • 没错。它确实删除了构造签名。但是,使用第二种方法中的Constructor&lt;T, U&gt; 方法感觉更惯用。
  • 可能更惯用,但不回答问题! :D 无论如何,谢谢!没想到解决办法这么简单!
【解决方案2】:

如果T 类型不包含任何索引签名,则删除构造函数和调用签名很简单:

type U = Pick<T, keyof T>

出于某种我不完全理解的原因,this breaks 带有索引签名。我的猜测是 Pick 在它的第二个参数中没有保留联合类型。一个完全万无一失的方法是:

type U = { [K in keyof T]: keyof T }

现在,这会删除 一个调用签名和一个构造函数签名。如果我们只想删除其中一个,我们可以将另一个添加回来。所以这些是最终的类型:

type OmitCallSignature<T> =
    { [K in keyof T]: T[K] } &
    (T extends new (...args: infer R) => infer S ? new (...args: R) => S : unknown)

type OmitConstructorSignature<T> =
    { [K in keyof T]: T[K] } &
    (T extends (...args: infer R) => infer S ? (...args: R) => S : unknown)

Playground

.

什么?你说这些类型mess up 带有重载的调用签名?哦,是的,他们确实搞砸了超载的呼叫签名。多可惜。老实说,我不知道如何解决这个问题。

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-12
  • 1970-01-01
  • 2022-07-21
  • 2021-11-30
  • 2018-07-13
相关资源
最近更新 更多