在TypeScript中声明动态添加的类属性

hea*_*der 8 properties class typescript tsc typescript-typings

我想在不知道TypeScript中属性名称,值和值类型的情况下将属性分配给类的实例.让我们假设我们有以下example.ts脚本:

// This could be a server response and could look totally diffent another time...
const someJson:string = '{ "foo": "bar", "bar": "baz" }'

class MyClass {
  someProperty:boolean

  constructor( json:string ) {
    const parsedJson:any = JSON.parse( json )

    Object.keys( parsedJson ).forEach(
      ( key:string ) => {
        this[ key ] = parsedJson[ key ]
      }
    )

    this['someProperty'] = true
  }
}

const myInstance = new MyClass( someJson )

// Works fine, logs `true`.
console.log( myInstance.someProperty )

// Error: Property 'foo' does not exist on type 'MyClass'.
console.log( myInstance.foo )

// Error: Property 'bar' does not exist on type 'MyClass'.
console.log( myInstance.bar )
Run Code Online (Sandbox Code Playgroud)

如何确保TypeScript编译器不会抱怨动态添加的属性,而是将它们作为"key": value任何类型的对处理.我仍然想tsc确保myInstance.someProperty必须是类型,boolean但我希望能够获得myInstance.whatever即使没有遇到编译器错误也没有定义.

我没有找到任何证明这一点的文件.也许是因为我不是母语为英语的人.所以请保持答案简单.

编辑:

我记得有类似下面的东西,但我从来没有这样做:

interface IMyClass {
  [name:string]: any
}
Run Code Online (Sandbox Code Playgroud)

van*_*owm 12

您可以将索引签名添加到您的类中:

class MyClass {
  [index: string]: any; //index signature

  someProperty:boolean

  constructor( json:string ) {
    const parsedJson:any = JSON.parse( json )

    Object.keys( parsedJson ).forEach(
      ( key:string ) => {
        this[ key ] = parsedJson[ key ]
      }
    )

    this['someProperty'] = true
  }
}
Run Code Online (Sandbox Code Playgroud)


Nit*_*mer 11

问题是您在运行时添加了新属性,编译器无法知道这一点.

如果您事先知道了属性名称,那么您可以这样做:

type Json = {
    foo: string;
    bar: string;
}

...

const myInstance = new MyClass(someJson) as MyClass & Json;
console.log(myInstance.foo) // no error
Run Code Online (Sandbox Code Playgroud)

编辑

如果您事先不知道这些属性,那么您不能这样做:

console.log(myInstance.foo);
Run Code Online (Sandbox Code Playgroud)

因为那时你知道这foo是收到的json的一部分,你可能会有类似的东西:

let key = getKeySomehow();
console.log(myInstance[key]);
Run Code Online (Sandbox Code Playgroud)

这应该没有编译器的错误,唯一的问题是编译器不知道返回值的类型,它将是any.

所以你可以这样做:

const myInstance = new MyClass(someJson) as MyClass & { [key: string]: string };
let foo = myInstance["foo"]; // type of foo is string
let someProperty = myInstance["someProperty"]; // type of someProperty is boolean
Run Code Online (Sandbox Code Playgroud)

第二次编辑

你知道道具,但不在课堂上,你可以这样做:

type ExtendedProperties<T> = { [P in keyof T]: T[P] };
function MyClassFactory<T>(json: string): MyClass & ExtendedProperties<T> {
    return new MyClass(json) as MyClass & ExtendedProperties<T>;
}
Run Code Online (Sandbox Code Playgroud)

然后你就像这样使用它:

type Json = {
    foo: string;
    bar: string;
};
const myInstance = MyClassFactory<Json>(someJson);
Run Code Online (Sandbox Code Playgroud)

请注意,这仅适用于typescript 2.1及更高版本.


Jul*_*ien 6

如果您想在实例化时通过对象动态添加类属性,并且该对象的类型信息可用,您可以通过这种方式很好地获得完整的类型安全(只要您不介意使用静态工厂方法):

class Augmentable {
 constructor(augment: any = {}) {
   Object.assign(this, augment)
 }
 static create<T extends typeof Augmentable, U>(this: T, augment?: U) {
   return new this(augment) as InstanceType<T> & U
 }
}
Run Code Online (Sandbox Code Playgroud)

这是使用(假)this参数来推断类的构造函数类型。然后构造实例,并将其强制转换为实例类型(使用InstanceType实用程序类型)和您传递给方法的 props 的推断类型的联合。

(我们可以直接强制转换为Augmentable & U,但是这种方式允许我们扩展类。)

例子

增强基本属性:

const hasIdProp = Augmentable.create({ id: 123 })
hasIdProp.id // number
Run Code Online (Sandbox Code Playgroud)

用方法扩充:

const hasIdProp = Augmentable.create({ id: 123 })
hasIdProp.id // number
Run Code Online (Sandbox Code Playgroud)

扩展和扩充,this访问方法扩充:

const withAddedMethod = Augmentable.create({
  sayHello() {
    return 'Hello World!'
  }
})


withAddedMethod.sayHello() // Properly typed, with signature and return value
Run Code Online (Sandbox Code Playgroud)

限制

增强的属性并不是类的真正组成部分,因此您不能扩展包含这些增强的类。对于某些用例来说,这可能是一个功能,其中增补是临时添加,不打算修改原型层次结构

向 中添加非增强参数也不容易.create(),但是一个简单的解决方法是简单地利用增强功能来完成与使用额外参数相同的事情。