'How to maintain proper typescript type on Vue component data?

I'm working on a Vue project that I'm updating to Vue 3 with typescript. I've found a strange behavior with the typescript parser/compiler. I have the following 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. Hovering over the data structure (vscode) shows that when it is created there it thinks the type is Child.

In this example, it's not really an issue because it can easily cast it. 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).

I've tried manually specifying the return type of data(), as well, with the same result.

Am I doing something wrong? Is this just how the Vue plugin works with Typescript?



Solution 1:[1]

As outlined here, the issue is that the data items are reactive, and in the wrapping/unwrapping, the class is lost.

The solution there indicates that using a shallowRef() is indicated (because keeping the instance raw for something so complex is preferred generally):

export default defineComponent({
        name: 'Test',

        setup() {
            return {
                test: shallowRef(new Child()),
            };
        },

      ...
        
    });

If you don't mind (or prefer) the wrapper, simply changing data to setup will fix the typing issue:

export default defineComponent({
        name: 'Test',

        setup() {
            return {
                test: new Child(),
            };
        },
        
        ...
    });

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Dov Rosenberg