我在弄清楚接口中定义构造函数的工作方式时遇到了一些麻烦。我可能完全误解了一些东西。但是我已经搜索了很长一段时间的答案,但找不到与此相关的任何内容。
如何在 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,这并不能解决问题。你不能定义构造函数的返回类型。它将返回“测试”。以下内容的签名: class test { constructor () { } } 似乎是“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 实现类?
接口中的构造签名不能在类中实现;它们仅用于定义定义“新”功能的现有 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 团队是如何做到的......
他们声明一个接口,然后声明一个名称与接口名称完全匹配的变量。这也是输入静态函数的方法。
lib.d.ts
中的示例:
interface Object {
toString(): string;
toLocaleString(): string;
// ... rest ...
}
declare var Object: {
new (value?: any): Object;
(): any;
(value: any): any;
// ... rest ...
}
我试过了,它就像魅力一样。
new
函数的实现?
好吧,具有构造签名的接口并不意味着由任何类实现(乍一看,这对于像我这样具有 C#/Java 背景的人来说可能看起来很奇怪,但给它一个机会)。略有不同。
暂时把它想象成一个带有调用签名的接口(就像Java世界中的@FunctionalInterface)。它的目的是描述一个函数类型..种类。所描述的签名应该由函数对象满足......但不仅仅是任何高级函数或方法。它应该是一个知道如何构造对象的函数,一个在使用 new
关键字时被调用的函数。
因此,带有构造签名的接口定义了构造函数的签名!您的类的构造函数应符合接口中定义的签名(将其视为构造函数实现接口)。它就像一个工厂!
下面是一小段代码,试图演示最常见的用法:
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");
来自官方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);
从设计的角度来看,在接口中指定构造函数要求并不常见。接口应该描述您可以对对象执行的操作。如果需要,应该允许实现接口的不同类需要不同的构造函数参数。
例如,如果我有一个接口:
interface ISimplePersistence {
load(id: number) : string;
save(id: number, data: string): void;
}
我可能有将数据存储为 cookie 的实现,它不需要构造函数参数,以及将数据存储在数据库中的版本,它需要一个连接字符串作为构造函数参数。
如果您仍然想在接口中定义构造函数,有一种肮脏的方法可以做到这一点,我曾经回答过这个问题:
Interfaces with construct signatures not type checking
new
关键字,并且只说它对创建实例有效,即var x = new MyClass();
。
Section 3.5.3.2 Construct Signatures
。
要实现预期的行为,您可以使用 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) { }
}
不会编译。
要扩展 Nils' answer,您还可以使用相同的技术制作通用的 new
功能:
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) { ... }
}
}
你可以改用类型
class SomeClass {
constructor(param1: string, param2: number) {
}
}
type Params = new (param1: string, param2: number) => SomeClass
type ConstructParams = ConstructorParameters<Params> // [string, number]
const objs: ComesFromString[] = [MadeFromString, AnotherOne, MoreString]
;现在,我将如何从这些创建实例?循环说:_.each(objs, (x) => makeObj(x)
?这将引发错误,因为x
属于ComesFromString
类型并且没有构造函数。ComesFromString
不是objs
的正确类型注释,因为它暗示每个objs
元素都是该类型的 instance,而不是它的构造函数。你想要objs: StringConstructable[]