Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Porting JavaScript to TypeScript: what do I do when Object.defineProperties is used on `this`?

I’m trying to port some JavaScript code to TypeScript.

One of the classes looks like this (playground link):

class Foo {
  constructor() {
    Object.defineProperties(this, {
      a: {
        value: 'a',
      },

      b: {
        value: 'b',
      },

      n: {
        writable: true,
        value: 0,
      },
    });

    assertFooApi(this);
  }

  reset() {
    this.a = 'a';
    this.b = 'b';
    this.n = 0;
  }
}

interface FooApi {
  a: string,
  b: string,
  n: number,
}

function assertFooApi(input: any): asserts input is FooApi {
  // intentionally fake
}

This reset function errors because Object.defineProperties doesn’t affect the type signature in typescript.

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

I did not write the original JavaScript code nor do I fully understand it. I’m looking for a way to define this‘s type without modifying any behavior. The less changes I make to its existing implementation, the better. Eventually, I’ll write automated tests around it so I can change its implementation, but as a step 1, I think porting it to typescript is the best bang for my buck.

This comment provides two workarounds to the problem. I think they would work if I didn’t need the workaround to apply to this. But as you can see (in the playground link above), using an assert function on this doesn’t modify its internal type.

If a class defines properties on this with Object.defineProperties, how do I use those properties in other internal functions without compilation errors?

>Solution :

I’m looking for a way to define this’s type without modifying any behavior

This is exactly what the declare keyword does. It creates type information but never emits any code.

It’s rarely used in production code because it’s so easy to make it lie to you about what type something really is. But, for cases like this you can add the properties with declare which tells the type system to expect them to be there.

IMPORTANT NOTE: Just be careful! If you change the code in defineProperties to create different properties, typescript can’t pair this up and raise type errors. This may lead to runtime crashes. This is not a proper solution. But when porting untyped code to typed code incrementally, sometimes this unsafety is an improvement.

class Foo {

  declare readonly a: string
  declare readonly b: string
  declare n: number

  constructor() {
    Object.defineProperties(this, {
      a: {
        value: 'a',
      },

      b: {
        value: 'b',
      },

      n: {
        writable: true,
        value: 0,
      },
    });
  }

  reset() {
    // changed these because they would be readonly
    console.log(this.a); // fine
    console.log(this.b); // fine

    this.n = 0; // fine
  }
}

See Playground

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading