【问题标题】:TypeScript data object class with Object.assign() properties from constructor argumentTypeScript 数据对象类,具有来自构造函数参数的 Object.assign() 属性
【发布时间】:2023-03-16 16:12:01
【问题描述】:

这是一个被构造为使用方法来扩充数据对象的类。

export default class ViewModel<T> {
  constructor(props: T) {
    Object.assign(this, props);
  }

  foo() {
    console.log('This class and children have methods as well as data');
  }
}

是否可以让 TypeScript 理解 T 的所有 props 都存在于 ViewModel&lt;T&gt; 的实例上?

type User = { id: number; name: string };

const m = new ViewModel<User>({ id: 1, name: 'Alice' });

m.foo(); // fine

console.log(m.id); // Property 'id' does not exist

(Playground)

如果没有,有没有更好的方法来包装一个普通的 JS 对象,其中包含访问其数据属性的方法?我没有附加到这个抽象类,在上述情况下使用UserViewModel 会很好,但是代码库已经有许多不同的T 类型,如果可能的话,我更愿意重新使用它们。 (例如,使 UserViewModel 引用 User 而不是复制该类型的大部分定义。)

我知道我还可以将接口与类一起导出,例如在 Extending `this` in Typescript class by Object.assign 中,但我还希望类中的方法知道 this 上可用的值。

【问题讨论】:

  • 有什么理由不能让视图模型拥有props?这使得解决方案像m.props.id 一样简单。
  • 这是一个预先存在的设计,因此需要对现有代码进行大量重构。但另外,从设计的角度来看,要求 .props 在任何地方破解类型系统是不可取的。

标签: typescript


【解决方案1】:

如果你扩展或实现的类型没有静态已知的成员,TypeScript 真的不喜欢让你使用class。像 T 这样的泛型类型不起作用:

class Foo<T> implements T {} // error! 
// A class can only implement an object type or intersection of 
// object types with statically known members.

Typescript 可以在类型系统中表示这样的类构造函数:

type BarConstructor = new <T>(props: T) => T & { foo(): void }; // okay

如果您有该类型的对象,您可以按照您想要的方式使用它ViewModel

function makeABarAndDoThings(Bar: BarConstructor) {
  const bar = new Bar({ a: 1, b: "2", c: true });
  bar.foo(); // okay
  bar.a; // okay
  bar.b; // okay
  bar.c; // okay
}

但你不能使用class 来构建编译器理解为那种泛型类型的构造函数。


请注意,当您编写class Foo {} 时,您同时引入了一个名为Foo 的命名type,对应于该类的一个实例,以及一个命名value称为Foo,类的构造函数。

由于我们不能直接使用您的 ViewModel 类实现,我们将重命名它,然后使用 type assertionstype aliases 创建一个名为 ViewModel 的类型和值随心所欲。

这是重命名的类:

class _ViewModel<T> {
  constructor(props: T) {
    Object.assign(this, props);
  }
  foo() {
    console.log("This class and children have methods as well as data");
  }
}

以下是新的类型和值组合:

type ViewModel<T> = _ViewModel<T> & T;
const ViewModel = _ViewModel as new <T>(props: T) => ViewModel<T>;

ViewModel&lt;T&gt; 类型定义为 _ViewModel&lt;T&gt;intersectionT 本身。新的ViewModel 值在运行时与_ViewModel 相同,但在编译时我们断言它是一个通用构造函数,它接受T 类型的参数并生成ViewModel&lt;T&gt;


让我们看看它是如何工作的:

type User = { id: number; name: string };

const m = new ViewModel<User>({ id: 1, name: "Alice" });

m.foo(); // fine
console.log(m.id); // also fine

现在我觉得不错。希望有帮助;祝你好运!

Link to code

【讨论】:

    猜你喜欢
    • 2017-09-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-11
    • 2021-03-31
    • 1970-01-01
    相关资源
    最近更新 更多