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.
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
}
}