3

I am trying to have typescript support on my Nuxt project.

I understood that I have to use Vue.extend when returning the component data like so:

import Vue from 'vue';

type Data = {
 a: number
}

export default Vue.extend({
 data():Data{
  const a = 3;
  return {
   a
  }
 }
})

However if my component have injected properties, it does not attach those properties to this type.

import Vue from 'vue';

type Data = {
 a: number
}

export default Vue.extend({
 
 inject: ['b'],
 
 data():Data{
  const a = 3;
  const c = this.b
            ^^^^
            // ~~ Property 'b' does not exist on type 'Readonly<Record<never,any>> & Vue' 
  return {
   a
  },
 
  methods:{
   my_method(){
     this.a // no problem here
     this.b // Error here
   }
  }

 }
})

Shouldn't infer also the injected type?

I am forced to use:

const that = this as any;

that I would like to avoid.

47ndr
  • 583
  • 5
  • 23

4 Answers4

2

You can try to explicitly type this inside data method


import Vue from 'vue';

type Data = {
  a: number
}

export default Vue.extend({

  inject: ['b'],

  data(this: { b: string }): Data {
    const a = 3;
    const c = this.b
    return {
      a
    }
  },

  methods: {
    my_method() {
      this.a // no problem here
      this.b // ok
    }
  }
})

Playground

You can take a look on Vue.extends type definition:

export interface VueConstructor<V extends Vue = Vue> {
  new <Data = object, Methods = object, Computed = object, PropNames extends string = never>(options?: ThisTypedComponentOptionsWithArrayProps<V, Data, Methods, Computed, PropNames>): CombinedVueInstance<V, Data, Methods, Computed, Record<PropNames, any>>;
  // ideally, the return type should just contain Props, not Record<keyof Props, any>. But TS requires to have Base constructors with the same return type.
  new <Data = object, Methods = object, Computed = object, Props = object>(options?: ThisTypedComponentOptionsWithRecordProps<V, Data, Methods, Computed, Props>): CombinedVueInstance<V, Data, Methods, Computed, Record<keyof Props, any>>;
  new (options?: ComponentOptions<V>): CombinedVueInstance<V, object, object, object, Record<keyof object, any>>;


// we are interested in first
  extend<Data, Methods, Computed, PropNames extends string = never>(options?: ThisTypedComponentOptionsWithArrayProps<V, Data, Methods, Computed, PropNames>): ExtendedVue<V, Data, Methods, Computed, Record<PropNames, any>>;




 // ......
  extend<Data, Methods, Computed, Props>(options?: ThisTypedComponentOptionsWithRecordProps<V, Data, Methods, Computed, Props>): ExtendedVue<V, Data, Methods, Computed, Props>;
  extend<PropNames extends string = never>(definition: FunctionalComponentOptions<Record<PropNames, any>, PropNames[]>): ExtendedVue<V, {}, {}, {}, Record<PropNames, any>>;
  extend<Props>(definition: FunctionalComponentOptions<Props, RecordPropsDefinition<Props>>): ExtendedVue<V, {}, {}, {}, Props>;
  extend(options?: ComponentOptions<V>): ExtendedVue<V, {}, {}, {}, {}>;
}

Hence, you can provide explicit generics:

export default Vue.extend<Data, { my_method: () => void }, object, 'b'>({

  inject: ['b'],

  data(): Data {
    const a = 3;
    const c = this.b
    return {
      a
    }
  },

  methods: {
    my_method() {
      this.a // no problem here
      this.b // ok
    }
  }
})
  • Actually it does not see `this.b` in `my_method`. It is giving me an error. – 47ndr Sep 02 '21 at 13:48
  • See here https://www.typescriptlang.org/play?#code/FASwtgDg9gTgLgAgGoFcCmCBmMpgQcgDd18BuYCuATwgwBEBDOBhAXgQG9gEEGAuBADsUYAEZoYwAL4U0AD2jwEAEzSYGKADaJUaAHTy4aQcoA8jZgBpOCMFQD6YNHAAWUZQIAUASjYA+BEIoEGUEKWsoUQArNABjOGt8UXw-Ty5uBBBBGPiBAG0k-ABdSwoeZSYGHwELFi4eHlioQQBnRBZ2AGZyBoQm1sRYtgRXEBa9UQyeGGcUGEFOKYaGJZkecLLbZzdlFoF6hrtHbfcfRd6eUfGWAHoboSgECBxRTTQ8Fwk0JcuXMYmEHcEFAANarDIyKTeIA there is no error – captain-yossarian from Ukraine Sep 02 '21 at 13:52
  • Sorry, you are right. It was another error. Thank you! – 47ndr Sep 02 '21 at 14:02
1

use vue-class-component and vue-property-decorator.

by using those library you can write powerful typescript code like this.

@Component({
  layout: 'form',
  head: { title: 'somr title' },
  directives: { onlyNumber: onlyNumberDirective }
})
export default class Login extends Vue {
  @Ref()
  readonly form!: HTMLFormElement;
  
  phone = '';
  password = '';
  
  someMethod() {}

  get someComputedVar() {}
}

also nuxt-property-decorator combine above library and make it more simpler.

  • I understood the "Class component" was dropped for Vue3. And it seems those projects have no updates since 1 year. Also I found this: https://stackoverflow.com/questions/59644684/what-will-happen-for-vuejs-projects-based-on-class-components-in-vuejs-v3-0 – 47ndr Sep 02 '21 at 13:42
  • nuxt use vue 2 and nuxt 3 does not released officially – علی سالمی Sep 02 '21 at 14:02
  • Yes, i know. But hopefully in the future it will use Vue 3. Anyway I don't think there will be much support on the Class components. Thank you anyway. – 47ndr Sep 02 '21 at 14:04
  • 1
    I also hope dev community build alternative library for vue 3 :) – علی سالمی Sep 02 '21 at 14:05
0

I also meet with this case and this is what I found as a solution: You should write a plugin for nuxt like below and add it to nuxt.config in plugins section.

So for the first create object with Vue.extend

export const MyService = Vue.extend({

  data(){
    return {
      ready: false
    }
  },

  watch: {
    '$accessor.auth.loggedIn'(success){
        if (!success) return;

         this.ready = true;
        //do something here ...
     }
  },

  methods: {
    //...
  },

  
})

Then export configure plugin function in this way:

export default function (ctx, inject) {

  const hookMixin = {
    created() {
      // nuxt context depends on $root object,
      // so here we assigning $root which just created

      const $root = this.$root;
      const myService = new MyService({
        beforeCreate() {
          this.$root = $root;
        }
      });

    //make $myService accessible globally
    Object.defineProperty(Vue.prototype, '$myService', {
      get() { return myService; }
    })

    },
  }

  // here adding global hook onMounted as mixin
  const mixins = ctx.app.mixins || [];
  mixins.push(hookMixin);
  ctx.app.mixins = mixins;
}

Here three steps you should do so this component is available globally.

  1. Register created a hook for nuxt, for awaiting when nuxt will be ready.
  2. Assign nuxt $root object from when your component was ready on beforeCreate, which you set via VueContructor options.
  3. Make service global to be accessible via $myService via Object.defineProperty.

In this example I use watch of store variable loggedIn. Also I use here typed-vuex, so $accessor is a global accessor to store. The logic is simple I wait when user been loggedIn then I'm doing some stuff.

Enjoy you custom component!

0

Is it Nuxt3?

Make sure to change

import Vue from 'vue'
export default Vue.extend({...})

to

import { defineComponent } from 'vue';
export default defineComponent({ ... })
Daniel Danielecki
  • 8,508
  • 6
  • 68
  • 94