【问题标题】:How does interfaces with construct signatures work?带有构造签名的接口如何工作?
【发布时间】:2012-11-04 14:44:51
【问题描述】:

我在弄清楚接口中定义构造函数的工作原理时遇到了一些麻烦。我可能完全误解了一些东西。但是我已经搜索了很长一段时间的答案,但找不到与此相关的任何内容。

如何在 TypeScript 类中实现以下接口:

interface MyInterface {
    new ( ... ) : MyInterface;
}

Anders Hejlsberg 在video 中创建了一个包含类似内容的界面(大约 14 分钟)。但是对于我的生活,我无法在课堂上实现这一点。

我可能误解了一些东西,我没有得到什么?

编辑:

澄清一下。对于“新(...)”,我的意思是“任何东西”。我的问题是我什至无法获得这个工作的最基本版本:

interface MyInterface {
    new () : MyInterface;
}

class test implements MyInterface {
    constructor () { }
}

这不是为我编译我在尝试编译时得到“类'test'声明接口'MyInterface'但没有实现它:类型'MyInterface'需要构造签名,但类型'test'缺少一个”。

编辑:

因此,根据反馈对此进行了更多研究。

interface MyInterface {
    new () : MyInterface;
}

class test implements MyInterface {
    constructor () => test { return this; }
}

TypeScript 无效,这并不能解决问题。你不能定义构造函数的返回类型。它将返回“测试”。以下人员签字: 类测试{ 构造函数 () { } } 似乎是“new () => test”(通过将鼠标悬停在在线编辑器中的“class”上并仅粘贴该代码获得)。这就是我们想要的,也是我认为的。

谁能提供一个实际编译的例子或类似的例子?

编辑(再次...):

所以我可能想出了一个想法,为什么可以在接口中定义它但不能在 TypeScript 类中实现。以下工作:

var MyClass = (function () {
    function MyClass() { }
    return MyClass;
})();

interface MyInterface {
    new () : MyInterface;
}

var testFunction = (foo: MyInterface) : void =>  { }
var bar = new MyClass();
testFunction(bar);

那么,这只是 TypeScript 的一个功能,可以让您与 javascript 交互吗?或者是否可以在 TypeScript 中实现它而无需使用 javascript 实现类?

【问题讨论】:

  • 抱歉之前的错误反馈。我还在寻找,老实说,我最好的猜测是 ctor 构造不应该能够返回类型,并且在接口中指定一个类型的能力可能是一个错误。
  • @asawyer 是的,这看起来很奇怪。更奇怪的是,如果 Anders Hejlsberg 会在官方视频中使用一些不可用的东西。
  • 我在 codeplex 网站上发布了一个问题。 typescript.codeplex.com/discussions/403413
  • 看起来和调用签名一样。与您无法创建实现构造签名的类相同,您也无法创建实现调用签名的类。

标签: typescript constructor interface


【解决方案1】:

从设计的角度来看,在接口中指定构造函数要求并不常见。接口应该描述您可以对对象执行的操作。如果需要,应该允许实现接口的不同类需要不同的构造函数参数。

例如,如果我有一个接口:

interface ISimplePersistence {
    load(id: number) : string;
    save(id: number, data: string): void;
}

我可能有将数据存储为 cookie 的实现,它不需要构造函数参数,以及将数据存储在数据库中的版本,它需要一个连接字符串作为构造函数参数。

如果你仍然想在接口中定义构造函数,有一个肮脏的方法可以做到这一点,我曾经回答过这个问题:

Interfaces with construct signatures not type checking

【讨论】:

  • 我同意通常不在接口中包含构造函数的想法。但是,如果无法在 TypescriptClass 中实现它,为什么还有可能呢? (不使用你的黑客)
  • 我在语言规范中找不到任何暗示可以在接口上指定构造函数的内容。第 4.11 节涵盖了 new 关键字,并且只说它对创建实例有效,即 var x = new MyClass();
  • @SteveFenton 我在规范中也找不到任何东西。编辑 - 在我发布此消息后不到三秒钟 - Section 3.5.3.2 Construct Signatures.
  • 这使得 Anders Hejlsberg 在 typescriptlang.org 网站上的官方视频中使用它更加有趣,大约 14 分钟(在我的原始帖子中提到)。然而,他并没有在课堂上实现它,因此是我的问题。
  • 我查看了视频 - Anders 创建的接口无法在 TypeScript 类中实现。
【解决方案2】:

接口中的构造签名不能在类中实现;它们仅用于定义定义“新”功能的现有 JS API。下面是一个涉及接口new 签名的示例:

interface ComesFromString {
    name: string;
}

interface StringConstructable {
    new(n: string): ComesFromString;
}

class MadeFromString implements ComesFromString {
    constructor (public name: string) {
        console.log('ctor invoked');
    }
}

function makeObj(n: StringConstructable) {
    return new n('hello!');
}

console.log(makeObj(MadeFromString).name);

这为您可以调用 makeObj 的内容创建了一个实际约束:

class Other implements ComesFromString {
    constructor (public name: string, count: number) {
    }
}

makeObj(Other); // Error! Other's constructor doesn't match StringConstructable

【讨论】:

  • 正如我在上次编辑问题时所怀疑的那样。有了这个,我认为我们可以称这个问题为已回答。
  • 更正式地说,实现接口的类是类的实例所具有的契约。由于类的实例不包含构造签名,因此它不能满足接口。
  • 在 TypeScript 的手册(文档)中突出显示这一点真是太好了。
  • @RyanCavanaugh,说你有一个接口数组const objs: ComesFromString[] = [MadeFromString, AnotherOne, MoreString];现在,我将如何从这些创建实例?循环说:_.each(objs, (x) => makeObj(x)?这将引发错误,因为 x 的类型为 ComesFromString 并且没有构造函数。
  • ComesFromString 不是objs 的正确类型注释,因为它暗示每个objs 元素都是该类型的instance,而不是它的构造函数。你想要objs: StringConstructable[]
【解决方案3】:

在搜索完全相同的问题时,我去查看 TypeScript 团队是如何做到的......

他们声明一个接口,然后声明一个名称与接口名称完全匹配的变量。这也是输入静态函数的方式。

来自lib.d.ts的示例:

interface Object {
    toString(): string;
    toLocaleString(): string;
    // ... rest ...
}
declare var Object: {
    new (value?: any): Object;
    (): any;
    (value: any): any;
    // ... rest ...
}

我试过了,效果很好。

【讨论】:

  • 我使用的是相同的模式,为了让它不那么难看,只需在一行中写“} declare {”,所以它看起来像一个单一的两步构造。
  • 这应该是公认的答案。更改库以满足接口要求是不合理的。
  • 我想到了相同的想法来创建相同类型的实例。 public reparse(statement: string): this { type t = new (statement: string) => this;让 t = this.constructor as t;返回新的 t(语句);标识符与三件事相关联:值、类型和命名空间。与名称相关联的类之类的东西可能会不同步
  • 你如何为new函数提供一个实现?
【解决方案4】:

要实现预期的行为,您可以使用 Decorators,即使这可能不是它们应该用于的用途。

这个

interface MyInterface {
    new ();
}

function MyInterfaceDecorator(constructor: MyInterface) {
}


@MyInterfaceDecorator
class TestClass {
    constructor () { }
}

编译没有问题。相比之下,下面对 TestClass 的定义

// error TS2345: Argument of type 'typeof TestClass' is not assignable to parameter of type 'MyInterface'.
@MyInterfaceDecorator
class TestClass {
    constructor (arg: string) { }
}

不会编译。

【讨论】:

    【解决方案5】:

    带有构造签名的接口并不意味着任何类都可以实现(乍一看,对于像我这样具有 C#/Java 背景的人来说,这可能看起来很奇怪,但给它一个机会)。略有不同。

    暂时把它想象成一个带有调用签名的接口(就像Java世界中的@FunctionalInterface)。它的目的是描述一个函数类型..种类。所描述的签名应该由函数对象满足......但不仅仅是任何高级函数或方法。它应该是一个知道如何构造对象的函数,一个在使用new关键字时被调用的函数。

    因此,带有构造签名的接口定义了构造函数的签名!应符合接口中定义的签名的类的构造函数(将其视为构造函数实现接口)。就像一个工厂!

    这是一个简短的 sn-p 代码,试图演示最常见的用法:

    interface ClassicInterface { // old school interface like in C#/Java
        method1();
        ...
        methodN();
    }
    
    interface Factory { //knows how to construct an object
        // NOTE: pay attention to the return type
        new (myNumberParam: number, myStringParam: string): ClassicInterface
    }
    
    class MyImplementation implements ClassicInterface {
        // The constructor looks like the signature described in Factory
        constructor(num: number, s: string) { } // obviously returns an instance of ClassicInterface
        method1() {}
        ...
        methodN() {}
    }
    
    class MyOtherImplementation implements ClassicInterface {
        // The constructor looks like the signature described in Factory
        constructor(n: number, s: string) { } // obviously returns an instance of ClassicInterface
        method1() {}
        ...
        methodN() {}
    }
    
    // And here is the polymorphism of construction
    function instantiateClassicInterface(ctor: Factory, myNumberParam: number, myStringParam: string): ClassicInterface {
        return new ctor(myNumberParam, myStringParam);
    }
    
    // And this is how we do it
    let iWantTheFirstImpl = instantiateClassicInterface(MyImplementation, 3.14, "smile");
    let iWantTheSecondImpl = instantiateClassicInterface(MyOtherImplementation, 42, "vafli");
    

    【讨论】:

      【解决方案6】:

      要扩展Nils'answer,您还可以使用相同的技术制作通用new-able 函数:

      interface MyArrayConstructor {
          <T>(...elements: Array<T>): MyArrayInstance<T>
          new <T> (...elements: Array<T>): MyArrayInstance<T>
      }
      
      // “Typecast” not the function itself, but another symbol,
      // so that the body of myArray will also benefit from
      // type-checking:
      export const MyArray = myArray as MyArrayConstructor
      
      interface MyArrayInstance<T> {
          push(...args: Array<T>): number
          slice(from?: number, to?:number): Array<T>
      }
      
      function myArray(...elements: Array<T>): MyArrayInstance<T> {
        return {
          push(...args) { ... },
          slice(from?: number, to?: number) { ... }
        }
      }
      

      【讨论】:

        【解决方案7】:

        来自官方documentation

        这是因为当一个类实现一个接口时,只检查类的实例端。由于构造函数位于静态端,因此不包含在此检查中。

        相反,您需要直接使用类的静态部分。在本例中,我们定义了两个接口,ClockConstructor 用于构造函数,ClockInterface 用于实例方法。然后,为方便起见,我们定义了一个构造函数 createClock 来创建传递给它的类型的实例:

        interface ClockConstructor {
          new (hour: number, minute: number): ClockInterface;
        }
        
        interface ClockInterface {
          tick(): void;
        }
        
        function createClock(
          ctor: ClockConstructor,
          hour: number,
          minute: number
        ): ClockInterface {
          return new ctor(hour, minute);
        }
        
        class DigitalClock implements ClockInterface {
          constructor(h: number, m: number) {}
          tick() {
            console.log("beep beep");
          }
        }
        
        class AnalogClock implements ClockInterface {
          constructor(h: number, m: number) {}
          tick() {
            console.log("tick tock");
          }
        }
        
        let digital = createClock(DigitalClock, 12, 17);
        let analog = createClock(AnalogClock, 7, 32);
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2012-10-08
          • 2018-09-14
          • 1970-01-01
          • 1970-01-01
          • 2020-02-02
          • 1970-01-01
          相关资源
          最近更新 更多