使用打字稿在构造函数中动态设置类属性

Posted

技术标签:

【中文标题】使用打字稿在构造函数中动态设置类属性【英文标题】:Use typescript to dynamically set class property in constructor 【发布时间】:2021-12-31 05:36:31 【问题描述】:

我正在尝试创建一个打字稿类MyClass,并在构造函数中动态设置实例属性:

const myInstance = new MyClass((
  someField: 'foo'
))

myInstance.someField // typescript should show this as type string

如何使用 typescript 创建MyClass 以将someField 显示为myInstance 的字符串属性?

我可以使用泛型吗?有可能吗?

【问题讨论】:

喜欢this,也许吧? TypeScript 不允许您使用 class 声明来执行此操作,因为对于 classinterfaces,密钥必须是静态已知的。相反,您需要编写使用类型断言或其他声明来告诉编译器MyClass 充当构造函数,生成具有动态键的某些对象类型。如果这对你有用,我可能会写一个答案(尽管我有 85% 的把握我之前写过这个答案,所以我应该会找到它)。如果它不起作用,请edit问题中的代码来演示不满意的用例。 这行得通!为了清楚起见,我编辑了your example。 虽然我很难理解这一行:“as new >(arg: T) => MyClassType 作为类型的“new”操作符是什么意思? 我不确定从 objectRecord<string, unknown> 的更改是否提高了清晰度。我想你做出这个改变是为了安抚一些(在我看来被误导的)linter 规则,但这并没有真正让任何事情变得更清楚(特别是如果你尝试为T 使用接口类型并发现它匹配object 但不匹配Record<string, unknown>)。除非您的问题是关于此类事情的,否则我计划在我发布的答案中留下object(当我有机会写下来时)。见github.com/microsoft/TypeScript/issues/… 【参考方案1】:

从概念上讲,您想说class MyClass<T extends object> extends Tclass MyClass<T extends object> implements T,但TypeScript 不允许class 实例或interface 具有动态键。 classinterface 声明的所有键必须是静态已知的。因此,如果您想要这种行为,您需要做的不是class 声明。

相反,您可以描述所需的MyClass<T> 实例的类型以及MyClass 类构造函数的类型,然后使用type assertion 告诉编译器您的实际构造函数对象属于该类型.

假设您想要的MyClass<T> 类实例具有T 的所有属性,外加一个名为someMethodIGuess() 的方法。那么你的MyClass<T> 类型可以这样定义:

type MyClass<T extends object> = T & 
    someMethodIGuess(): void;

您希望您的MyClass 类构造函数有一个construct signature,它为某些generic T 对象类型接受T 类型的参数,并生成MyClass&lt;T&gt; 的实例。可以这样定义:

type MyClassConstructor = 
    new <T extends object>(arg: T): MyClass<T>

要实现这个类,我们可以使用 [Object.assign()] 将构造函数参数属性以及我们需要的任何方法或其他东西复制到实例中。

const MyClass = class MyClass 
    constructor(arg: any) 
        Object.assign(this, arg);
    
    someMethodIGuess() 

    
 

但是当然,如​​果我们这样离开它,编译器将不会将MyClass 视为MyClassConstructor(毕竟它不能有动态键,我什至没有尝试告诉它@ 987654348@ 这里)。我们需要一个类型断言来告诉它,像这样:

const MyClass = class MyClass 
    constructor(arg: any) 
        Object.assign(this, arg);
    
    someMethodIGuess() 

    
 as MyClassConstructor;

编译没有错误。再次注意,编译器无法理解 MyClass 实现符合 MyClassConstructor 类型。通过使用类型断言,我已经将确保正确实现它的负担从编译器(它不能这样做)转移到我(或者如果你使用这段代码的话)。所以我们应该非常小心地检查我们做了什么以及我们没有写错东西(例如,Object.assign(arg, this); 而不是Object.assign(this, arg); 会是一个问题)。


让我们测试一下:

const myInstance = new MyClass((
    someField: 'foo'
))

console.log(myInstance.someField.toUpperCase()); // FOO
myInstance.someMethodIGuess(); // okay

看起来不错!编译器期望myInstance 具有string 类型的someField 属性,以及someMethodIGuess() 方法。


就是这样。请注意,使用 MyClass 的方式与使用另一个类构造函数相同。例如,编译器永远不会对具有动态键的 class 声明感到满意,因此如果您尝试使用 class 声明创建 MyClass 的通用子类,您将收到错误:

class SubClass<T extends object> extends MyClass<T>  // error    
    anotherMethod()  

如果你需要这种东西,你会发现自己必须使用与以前相同的技巧来处理子类:

type SubClass<T extends object> = MyClass<T> &  anotherMethod(): void ;
type SubClassConstructor =  new <T extends object>(arg: T): SubClass<T> ;
const SubClass = class SubClass extends MyClass<any> 
    anotherMethod()  
 as SubClassConstructor;

Playground link to code

【讨论】:

【参考方案2】:

这个怎么样?

class MyClass
    constructor(props: any) 
        Object.assign(this, props);
    

    baz(): string 
        return "Hello World";
    

    static create<T>(props =  as T) 
        return new MyClass(props || ) as MyClass & T
    


const o = MyClass.create(
    foo: 1,
    bar: "Hello World"
);

console.log(o.foo);
console.log(o.bar);
console.log(o.baz());


TS playground

【讨论】:

谢谢,但我想用“new”运算符来呼叫课程。所以这对我不起作用。但再次感谢您!

以上是关于使用打字稿在构造函数中动态设置类属性的主要内容,如果未能解决你的问题,请参考以下文章

为啥打字稿在实现抽象函数时忽略严格的空检查?

如何在打字稿中使用可选参数调用类构造函数[重复]

类装饰器上的打字稿文档-返回“类扩展构造函数”的函数

打字稿:通过传入命名参数的构造函数创建类?

从类创建派生类型,但省略构造函数(打字稿)

成员函数指针作为构造函数参数