typescriptvuejs3javascript-decoratorstypescript-5

How to turn the class definition to Vue 3 component definition with "tc39" decorators?


The API purposed in tc39/proposal-decorators is pretty different with previous decorators API. Although the TypeScript 5 does not fully support the new API yet, the deprecating of the previous API is the matter of time, so I'll develop with the newest API.

The answer to question Into what we need to convert the TypeScript class using decorators to get the valid Vue component? is actual for the old API, but now it is required to solve same problem with newest API.

The initial code is

import { defineComponent } from "vue";

/* [ Theory ] Overrides the eponymous type of "typescript/lib/lib.decorators.d.ts".
*  [ Reference ] https://github.com/tc39/proposal-decorators#classes */
type ClassDecorator = (value: Function, context: {
  kind: "class";
  name: string | undefined;
  addInitializer: (initializer: () => void) => void;
}) => Function | void;


const VueComponentOptions: ClassDecorator = (targetClass: Function, context: ClassDecoratorContext): Function | void => {

  // TODO check for the kind === "class"

  const vueOptions: ComponentOptions = {
    methods: {},
    computed: {}
  };

  return defineComponent(vueOptions);

};

Currently, the usage

/* https://stackoverflow.com/questions/75909821/the-classdecorator-type-defined-in-tc39-proposal-decorators-is-not-applicabl */
/* @ts-ignore TS1270 TS decorators are not fully TC39-compatible yet. */
@VueComponentOptions
export default class ReactiveStateExperimentalSample {

}

causes the error

ReactiveState-ExperimentalSample.vue?../../node_modules/ts-loader/index.js??clonedRuleSet-1!../../node_modules/vue-loader/dist/index.js??ruleSet%5B1%5D.rules%5B10%5D.use%5B0%5D:7
Uncaught TypeError: Function expected
    ... ```

The cause is

If any other type of value besides a function is returned, an error will be thrown.

https://github.com/tc39/proposal-decorators#classes

Maybe I need to wrap the defineComponent to function?

const VueComponentOptions: ClassDecorator = (targetClass: Function, context: ClassDecoratorContext): Function | void => {

  const vueOptions: ComponentOptions = {
    methods: {},
    computed: {}
  };

  return (): ReturnType<typeof defineComponent> => defineComponent(vueOptions);

};

This time, there are no the JavaScript runtime error, but the Vue will emit the warning:

runtime-core.esm-bundler.js:170 [Vue warn]: Invalid VNode type: undefined (undefined) at at

and of course, the component will not be rendered.

What I need to do inside VueComponentOptions for the newest decorators API + Vue 3 case?

Please don' recommend the third-party libraries because this question is focused on implementation.


Solution

    1. You are trying to:
      • Reimplement a newer alternative of vue-class-component: a class that returns ReturnType<typeof defineComponent>, with tc39 decorators

    Please correct me if I understood incorrectly.


    <script lang="ts">
    import { ComponentOptions, defineComponent } from 'vue';
    import type { ComponentPublicInstance } from 'vue';
    
    // vue\packages\runtime-core\src\component.ts
    interface ClassComponent {
      new (...args: any[]): ComponentPublicInstance
      // here is where you put 
      __vccOpts: ComponentOptions
    }
    
    // you need a class saying it implements ComponentPublicInstance for this.$props and other acessors
    const Base = class Base {} as any as ClassComponent;
    
    
    @(function VueComponentOptions(cls) { // inplace to see implecit typings
      cls.__vccOpts = defineComponent({
        name: cls.name,
        // maybe like this or whatever way you want.
        // ClassComponent used a lot of tricks here.
        //   like co
        data: () => new cls(),
        props: ['msg'],
        // whatever else you need
      })
      // return undefined
    })
    export default class MyComponent extends Base {
      text = 'from class';
      fn() {
        this.text
        this.$props; // has acess to all things in ComponentPublicInstance 
      }
    }
    </script>
    
    <template>
      <h1>{{ msg }} {{ text }}</h1>
    </template>
    

    See an implementation of alike thing for more info, https://github.com/facing-dev/vue-facing-decorator/blob/master/src/index.ts#L29


    If you want to be able to use both @Ops class and @Ops({...etc}) class you'll need an (data: object) => ClassDecorator overload


    edit: I forgot about __vccOpts, here they are now