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?
3
Answers
I guess this counts as an answer, but I'm hoping there is a better one out there. If you implement
reset
like this, the errors go away (playground link):Ideally, I wouldn't have to put that at the top of every internal function.
To convert this code to TS without changing its behaviour,
declare
class fields (declare
d fields compile to nothing)readonly
for non-writable fieldsimplement
your interface to make sure it’s properly implementedRemember, that constructor’s return value type doesn’t affect class type in any way. Only fields, methods, and
extends
clause do.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.See Playground