this.data not properly retaining typescript types when used in methods
Version
3.2.33
Reproduction link
Steps to reproduce
Using Vue 3 and typescript, define a component:
export default defineComponent({
name: 'Test',
data() {
return {
test: new Child(),
};
},
methods: {
testMethod: function () {
this.test = new Child();
let test2 = new Child();
}
},
});
class Child {
b = 0;
}
In the method, the parser thinks that test2 is of type Child. But it thinks that this.test is of type {b: number} (i.e. it has lost the connection to Child).
In this example, it's not a huge deal, because it could easily be recast. But if Child is instead defined as:
class Parent { private a = 1; } class Child extends Parent { b = 0; }
then this.test still has the same type {b: number} (it loses the private member) and can no longer be cast to Parent. This causes endless typecasting to any
to work around.
Another symptom it isn't picking up the right type is that something like this.test.a = 3 generates property does not exist (ts2339) while test2.a = 3; generates property is private and only accessible with parent (ts2341) (which is the correct error).
What is expected?
this.test should have type Child
What is actually happening?
this.test has type {b: number}
this is caused by Ref-Unwrapping happening in reactive objects. As a side effect, classes are essentially reduced to their interface.
As you said, when using simple classes, it's not a big deal in most situations. when using more complex ones with private fields, internal state, side effects and so forth, I would argue that it's better to keep the instance raw anyway, and (at least in composition API, which has better Typescript inference), this would then work as you expect:
import { defineComponent, ref, shallowRef } from 'vue'
class Parent {
private a = 1
}
class Child extends Parent {
b = 0;
};
export default defineComponent({
name: 'Test',
setup() {
const child = shallowRef(new Child())
const functionInSetup = () => {
const _child = child.value
_child.a = 0
}
return {
child
}
},
methods: {
testMethod: function () {
// interestingly enough, also works in Options API methods when
// defined in setup with shallowRef
let child = this.child
child.a = 0
}
},
});
For Options API, for now, you would have to cast the value, unfortunately. Because even when doing the same thing in data
that works in setup
, it doesn't infer correctly:
data () {
return {
// still won't be recognized as instance of `Child` in options API methods.
child: shallowRef(new Child())
}
}
MAybe some TS guru wants to take a stab at this, but I'm not too optimistic.
This is super helpful, @LinusBorg. Thank you. I've had an outstanding question on stack overflow for a couple of months, if you want to post the same answer there for some credit. :)