英文:
How to write a constructor for a type
问题
const NamedPerson: PersonConstructor = class {
constructor(givenName: string, familyName: string) {
return { givenName, familyName };
}
};
英文:
In TypeScript, I can define a type like this:
type Person = {
givenName: string
familyName: string
}
And I can define a constructor type for my type like this:
type PersonConstructor = {
new (givenName: string, familyName: string): Person
}
But I can't figure out the proper syntax for the constructor function implementation. The code below is invalid, and produces an error:
const NamedPerson: PersonConstructor = (givenName: string, familyName: string) => {
return { givenName, familyName }
}
Type '(givenName: string, familyName: string) => { givenName: string; familyName: string; }' is not assignable to type 'PersonConstructor'.
Type '(givenName: string, familyName: string) => { givenName: string; familyName: string; }' provides no match for the signature 'new (givenName: string, familyName: string): Person'.ts(2322)":
I can call the constructor, no problem, so the constructor type definition looks ok I think:
const me = new NamedPerson('First', 'Last');
What is the proper syntax for an implementation of PersonConstructor
?
答案1
得分: 1
以下是翻译好的部分:
const NamedPerson: PersonConstructor = class {
constructor(public givenName: string, public familyName: string) { }
}
const person = new NamedPerson("John", "Doe");
console.log(person.familyName.toUpperCase()) // "DOE"
console.log(person instanceof NamedPerson); // true
在上面的 class
表达式中,我使用了参数属性来使定义变得更容易,但您不需要使用它。
如果您想要使用任意函数,TypeScript 并不想让这变得容易。ES5 风格的构造函数在运行时工作(它们将属性分配给 this
而不是返回任何内容),但 TypeScript 不想为此添加支持,建议使用 "class",参见 microsoft/TypeScript#2310。从构造函数返回值在运行时也可以工作,但 TypeScript 目前不允许您将类构造函数标记为生成除 this
类型之外的东西:参见 microsoft/TypeScript#27594 以获取相关功能请求。并且 TypeScript 实际上无法区分 function
表达式(可以使用 new
)和箭头函数(如果尝试 new
它们,即使从不尝试在其中使用 this
,它们也会在运行时失败),因此即使上述两个问题得到解决,您也需要小心确保您的函数不是错误类型的函数。
目前,处理所有这些需要某种包装函数,并使用类型断言来抑制编译器警告,并使箭头函数可用。也许像这样:
function toConstructor<A extends any[], R extends object>(
fn: (...args: A) => R): new (...args: A) => R {
return class {
constructor(...args: any) {
return fn(...args);
}
} as any;
}
然后您可以这样使用它:
```typescript
const NamedPerson: PersonConstructor =
toConstructor((givenName: string, familyName: string) => {
return { givenName, familyName }
});
const person = new NamedPerson("John", "Doe");
console.log(person.familyName.toUpperCase()) // "DOE"
console.log(person instanceof NamedPerson); // false
哦,是的,person instanceof NamedPerson
是 false
,这很令人困惑。这是构造函数返回东西而不是分配给 this
的缺点之一;原型没有正确设置。因此,即使 TypeScript 使使用任意函数作为类构造函数变得相对容易,它们仍会导致奇怪的行为。
如果您希望在运行时和编译器中尽量减少问题,您应该将类用作类,将函数用作函数,而不要混合在一起。如果您有一个函数,那么最好只将其用作函数,而不涉及 new 操作符:
const namedPerson = (givenName: string, familyName: string): Person => {
return { givenName, familyName }
}
const person = namedPerson("John", "Doe");
console.log(person.familyName.toUpperCase()) // "DOE"
英文:
The correct way to implement a construct signature is with a class
declaration or expression:
const NamedPerson: PersonConstructor = class {
constructor(public givenName: string, public familyName: string) { }
}
const person = new NamedPerson("John", "Doe");
console.log(person.familyName.toUpperCase()) // "DOE"
console.log(person instanceof NamedPerson); // true
In the above class
expression I've used parameter properties to make the definition a little easier, but you don't need to use this.
If you want to use an arbitrary function instead, TypeScript doesn't want to make that easy for you. ES5-style constructor functions work at runtime (where they assign properties to this
instead of returning anything) but TypeScript doesn't want to add support for that and the advice is "use class
es": see microsoft/TypeScript#2310. Returning values from your constructor also works at runtime, but TypeScript doesn't currently let you mark class constructors as producing something other than its this
type: see microsoft/TypeScript#27594 for the relevant feature request. And TypeScript can't really tell the difference between function
expressions (which are new
able) and arrow functions (which explode at runtime if you new
them, even if you never try to use this
inside them), so even if the above two issues were addressed, you'd need to be careful that your function wasn't the wrong kind of function.
Right now, handling all of this requires some kind of wrapper function with type assertions to suppress compiler warnings, and an implementation that makes arrow functions usable. Maybe like this:
function toConstructor<A extends any[], R extends object>(
fn: (...args: A) => R): new (...args: A) => R {
return class {
constructor(...args: any) {
return fn(...args);
}
} as any;
}
And then you'd use it like
const NamedPerson: PersonConstructor =
toConstructor((givenName: string, familyName: string) => {
return { givenName, familyName }
});
const person = new NamedPerson("John", "Doe");
console.log(person.familyName.toUpperCase()) // "DOE"
console.log(person instanceof NamedPerson); // false
Oh, yeah, person instanceof NamedPerson
is false
, which is confusing. That's one of the drawbacks of constructors that return things instead of assigning to this
; the prototype isn't set properly. So even if TypeScript made it easy-ish to use arbitrary functions as class constructors, they'll lead to weird behavior.
If you want to minimize problems at runtime and with the compiler, you should use classes as classes and functions as functions and not mix them together. If you've got a function then you'll have a better time just using it as a function without involving the new
operator at all:
const namedPerson = (givenName: string, familyName: string): Person => {
return { givenName, familyName }
}
const person = namedPerson("John", "Doe");
console.log(person.familyName.toUpperCase()) // "DOE"
答案2
得分: 0
经过一些提示和大量阅读,答案是:
type Person = {
givenName: string
familyName: string
}
type PersonConstructor = {
new (givenName: string, familyName: string): Person
}
const NamedPerson: PersonConstructor = class {
givenName: string
familyName: string
constructor(givenName: string, familyName: string) {
this.givenName = givenName
this.familyName = familyName
}
}
const me = new NamedPerson('First', 'Last');
然而,这需要我多次写出Person
的所有字段,考虑到我有100多个类型别名,每个类型别名都有十几个字段,这并不值得。
英文:
After a few hints and much reading, the answer turns out to be:
type Person = {
givenName: string
familyName: string
}
type PersonConstructor = {
new (givenName: string, familyName: string): Person
}
const NamedPerson: PersonConstructor = class {
givenName: string
familyName: string
constructor(givenName: string, familyName: string) {
this.givenName = givenName
this.familyName = familyName
}
}
const me = new NamedPerson('First', 'Last');
However this requires me to write out all of the fields of Person
multiple times, and it's not worth it considering I have over 100 type aliases and each one has a dozen or so fields.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论