如何为类型编写构造函数

huangapple go评论69阅读模式
英文:

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

以下是翻译好的部分:

正确实现构造签名的方法是使用 class 声明或表达式:

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 NamedPersonfalse,这很令人困惑。这是构造函数返回东西而不是分配给 this 的缺点之一;原型没有正确设置。因此,即使 TypeScript 使使用任意函数作为类构造函数变得相对容易,它们仍会导致奇怪的行为。

如果您希望在运行时和编译器中尽量减少问题,您应该将类用作类,将函数用作函数,而不要混合在一起。如果您有一个函数,那么最好只将其用作函数,而不涉及 new 操作符

const namedPerson = (givenName: string, familyName: string): Person => {
  return { givenName, familyName }
}

const person = namedPerson("John", "Doe");
console.log(person.familyName.toUpperCase()) // "DOE"

[Playground 链接到代码](https://www.typescriptlang.org/play?#code/HYQwtgpgzgDiDGEAEApeIA2AvJBvAUEkgC4CeMyAChAE5QD2wSAvHoUUgOYCWAbhMABy4CAC4kUYjW7BO7IgDNw3DKWGRxk6bPYBffOzIUk1OowDCjLQFd4xejRZsOSYBADuACh78hIzVIynAA0SEpgKmr+EoGyAJTipgzAegZEoJCwCMjmGCBQUACiAB4wjgTySPBWxEjqEAAmSYyJtMmWwDZ2Dk7weQXOLlU1NLb2NJ4w1gBGGNzwXHwC9QHaIUhTs-NhyqorMWtxeEj6LvqV1Z21FGZMrG7udSJNbYyeAEQo9AAWwO+h7wAIvQIO84gBuC5WegYCAAOgw9E4k1ewDh4Ui9Th9gAqjAbuZ8hBPHEjgB6MlIIEAeUK7yhnRh8MRyJuySQMkkIGAiHoCiekBetwhSApJFGEDSJ3YGWgcEQSAA6jQQDBBkRLpIBY1msBWrcOl1xixKkR7IapGMHJ5vEs-BoDkFQhi9tEtEEjswAHzqoY0CDEaw0Ji4Ra+erO3ZRSDSoYnCFSxTWHnEbiMEj0C2jbo0AA8AEEkBBisQBA0oEhuaQANoAXVCACUiyWyxX6NMAFYQOxezymsJ6pCeOEjkA0ThQcT5z0+hsJVweIcjuFjidTmdIJsEOP+wPBqr9CvbuPDK7Z8bD0fjyeV4CkI7Hk9IXdBpgKYCXlfXhNP05DXSVhWVaQmcianlqbLpvci71EKyQfF8vz-FSwKgj+HCakyCJIiitzolGWK4vitCElAx

英文:

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(&quot;John&quot;, &quot;Doe&quot;);
console.log(person.familyName.toUpperCase()) // &quot;DOE&quot;
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 classes": 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 newable) 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&lt;A extends any[], R extends object&gt;(
  fn: (...args: A) =&gt; R): new (...args: A) =&gt; 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) =&gt; {
    return { givenName, familyName }
  });

const person = new NamedPerson(&quot;John&quot;, &quot;Doe&quot;);
console.log(person.familyName.toUpperCase()) // &quot;DOE&quot;
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 =&gt; {
  return { givenName, familyName }
}

const person = namedPerson(&quot;John&quot;, &quot;Doe&quot;);
console.log(person.familyName.toUpperCase()) // &quot;DOE&quot;

Playground link to code

答案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(&#39;First&#39;, &#39;Last&#39;);

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.

huangapple
  • 本文由 发表于 2023年6月2日 12:17:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/76387080.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定