DEV Community

Cover image for Using Vue with typescript and Vue.extend
Mateus Amorim
Mateus Amorim

Posted on

Using Vue with typescript and Vue.extend

Introduction

Hey there, I just wanted to share some of my experience trying to use typescript with Vue.extend, since I didn't find many resources at first. This is not meant to be an in-depth tutorial, but rather to provide some how-to's, good practices, and tips.

Why Vue.extend instead of classes?

When I had to make this decision, it was uncertain if the new Composition API would work with the class API, so Vue.extend was a safer choice and also a softer transition for most, making it easier to adapt.

How to use Vue.extend?

to use Vue.extend you need to either be in a typescript file or use lang="ts" in your script tag.

after that, you can create your component passing its options to Vue.extend() first argument, just as you would with js and, for the most part, you will have the proper typings inferred.

const myComponent = Vue.extend({
  // options...
});

export default myComponent;

Gotchas

Computed values

Some times you might get an error saying this.something does not exist in ... while in fact, it does! This is a very confusing error that happens mostly because of computed values with multiple returns types.

What is happening here is that, since we could not infer the value return type properly, the ExtendedVue type did not compute correctly, which leads to this misleading error.

To fix it, you can add return types to computed values manually, like this:

computed: {
  myValue(): string | number {
    //...
  }
}

Good practices

Named exports

Typescript, with the help of some plugins. like ts-importer for vsCode, will auto-import symbols created via named exports, so instead of using export default Vue.extend(...) it is better to declare a constant for the component name and export it later.

I also recommend using prefixes and suffixes for the components so they are easy to import and identify when using auto-imports.

Obs: Auto import suggestions do not work in templates (but some plugins help with that). I recommend assigning the import command from ts-importer to a hotkey, that way you can use it anywhere.

Objects/Arrays/Enums and other complex types for props

If your prop is any of those, you should use the PropOptions type, which is included in Vue, to assign the desired type to the prop, ex:

import { PropOptions } from 'vue';

//...

props: {
  items: {
    type: Array,
    default() { return [] }
  } as PropOptions<{ foo: string, bar: string }[]>
}

Undefined Props

The props, by default, will assume they are defined, so, you should do one of the following:

  • if providing just the defined prop type, make sure the prop is required or has a default value.

  • If for some reason it's ok for the prop to be undefined, use PropOptions and a union type with undefined and the prop type.

Inheritance

Vue.extend can be used for inheritance, since it extends other components, but I really recommend not doing so as you will lose some of the typing and it will be hard to properly use the interfaces for other things. Instead, try to use things like functional components.

Using props in other components

When you use Vue.extend, the output will be constructed based on this type:

ExtendedVue<Instance extends Vue, Data, Methods, Computed, Props>

With that in mind, you can create a simple helper to extract the props from the interface:

export type ComponentProps<T> = T extends ExtendedVue<
  any,
  any,
  any,
  any,
  infer Props
>
  ? Props
  : any;

Here we are saying: if T, which can be any type, is something that has everything ExtendedVue has, take the type of Props and return it. Otherwise, return any.

Now you can use computed values and others to link the props types in other components:

computed: {
  items(): ComponentProps<typeof MyListComponent>['items'] {
    //...
  }
}

This will enable IntelliSense from typescript while preserving jsDocs, having the options to "go to the reference" and keeping only one source of truth for the props.

Templates type safety

We don't have type safety in the templates, but that can be "changed" a little with the help of vetur for VsCode.

It has an experimental option for template interpolation which will show errors when a type is incorrect. I recommend enabling it, but be aware that this is only enforced in the editor, not in the build. Also, seems like it does not work well with CRLF line breaks, so if you are having problems with it, try changing the file line breaks to LF (in VsCode this can be done by selecting everything and clicking CRLF at the bottom right, then clicking LF).

Vuex type safety

Vuex type safety is kinda complex. For creating the actions and others it is ok, but when using them we do not have type safety.

To solve that, there are a few modules you can use. I did a little experiment to implement type safety easily with this little package here:

https://www.npmjs.com/package/typed-vuex-store

But I do not recommend using it as it will probably stay as an experiment for a while. Instead, it seems like someone did something similar:

https://www.npmjs.com/package/direct-vuex

This makes it easy to use Vuex in a type-safe manner, but there are a few drawbacks:

  • We are creating circular dependencies (which might not be a problem)
  • We are replacing the usage of this.$store and the ctx, which makes testing a little bit harder to do.

A solution for those is to use this one instead:

which is kinda bothersome since you have to repeat more code and use this.$store all the time, but it might work better with tests.

There are also some other solutions out there and the ones presented here might not work well with some frameworks like nuxt, I recommend exploring a little to see what would work best for your case.

Discussion (0)