Vue.extend or vue-class-component

There are two recommended forms for writing Vue components in TypeScript:

  • Vue.extend(): Using the base Vue constructor, create a “subclass”. This is most similar to the Vue single-file component standard form, except that the component options need to be wrapped inVue.extend()In the.
  • vue-class-component: usually withvue-property-decoratorUsed together, it provides a series of decorators that allow us to write class-style Vue components.

The output is the same, creating a Vue subclass, but there are some differences when writing component options such as props and mixin. Especially when you use vue.extend (), you’ll have to do some extra processing in order for TypeScript to infer types correctly. Next, let’s talk about the differences in detail.

Prop

Because component instances are isolated in scope, we typically use the Prop option when passing data from parent components to child components. At the same time, to ensure the type safety of Prop, we will add the specified type validation to Prop as follows:

export default {
  props: {
    someProp: {
      type: Object.required: true.default: (a)= > ({ message: 'test'})}}}Copy the code

We define a someProp whose type is Object.

There’s nothing wrong with this when using JavaScript, but it’s a bit of a disadvantage when using TypeScript. We don’t get much useful information about someProp (such as its properties), and even from TypeScript’s point of view, This will be of type any:

This means that we can use any property (present or not) on someProp that can be compiled. To prevent this from happening, we will add type comments to Prop.

Vue.extend()

When you add a type annotation to the vue.extend () method, you give the type assertion:

import Vue from 'vue'

interface User {
  name: string,
  age: number
}

export default Vue.extend({
  props: {
    testProps: {
      type: Object as () => User
    }
  }
})

Copy the code

When accessing testProps within a component, you get a hint:

However, you must declare in the form of the return value of the function, not directly:

export default Vue.extend({
  props: {
    testProps: {
      type: Object as User
    }
  }
})
Copy the code

It gives an error warning that the User interface does not implement the methods that the native Object constructor executes: Type ‘ObjectConstructor’ cannot be converted to type ‘User’. Property ‘id’ is missing in type ‘ObjectConstructor’.

In fact, we can use Prop Type declaration:

export type Prop<T> = { (): T } | { new(... args:any[]): T & object }

export type PropValidator<T> = PropOptions<T> | Prop<T> | Prop<T>[];

export interface PropOptions<T=any> {
  type? : Prop<T> | Prop<T>[]; required? :boolean;
  default? : T |null | undefined | (() => object);
  validator? (value: T) :boolean;
}

Copy the code

Prop Type can appear in two different ways:

  • A generic type that contains a calling signature that returns T;
  • A signature of a generic constructor that creates an object of the specified type T (return value)T & objectUsed to reduce precedence, the first is taken when both approaches are met, and it can also be used to flag that a constructor should not return a primitive type.

When we specify String type type/Number/Boolean/Array/Object/Date/Function/Symbol primary constructor, Prop will return to their respective signature return values.

When type is a String constructor, its call signature is returned as String:

// lib.es5.d.ts
interface StringConstructor {
  new(value? : any):String; (value? : any): string; readonly prototype:String; fromCharCode(... codes: number[]): string; }Copy the code

This is why, when the Object constructor is specified as type, TypeScript deduces that type to be any after processing the Vue declaration file:

interface ObjectConstructor {
  new(value? : any):Object;
  (): any;
  (value: any): any;
  // Other attributes....
}

Copy the code

Similarly, when we use the keyword as to assert that Object is () => User, it can infer that it is User.

From the second part of Type, we can also pass in custom classes in addition to the native constructor:

In addition, here is a PR that exposes a more intuitive type (available only in Vue 2.6) :

props: {
  testProp: Object as PropTypes<{ test: boolean }>
}
Copy the code

vue-class-component

Thanks to the Vue-Propperty-decorator Prop decorator, this becomes easy when adding type inference to a Prop:

import { Component, Vue, Prop } from 'vue-property-decorator'

@Component
export default class Test extends Vue {
  @Prop({ type: Object })
  private test: { value: string}}Copy the code

When we access test within the component, we get its correct type information.

mixins

Mixins are a way to distribute reusable functionality in Vue components. When using it in TypeScript, we want to get type information about mixins.

This is a bit difficult when you use vue.extends (), which does not infer the types in mixins:

// ExampleMixin.vue
export default Vue.extend({
  data () {
    return {
      testValue: 'test'}}})// other.vue
export default Vue.extend({
  mixins: [ExampleMixin],
  created () {
    this.testValue // error, testValue does not exist!}})Copy the code

We need to modify it slightly:

// other.vue
export default ExampleMixin.extend({
  mixins: [ExampleMixin],
  created () {
    this.testValue // The compiler passes}})Copy the code

There is a problem, however, that this will not work when multiple mixins are used and the type is inferred. And in this Issuse it is also made clear that this cannot be changed.

Vue-class-component would be a lot easier:

// ExampleMixin.vue
import Vue from 'vue'
import Component from 'vue-class-component'

@Component
export class ExampleMixin extends Vue {
  public testValue = 'test'
}

// other.vue
import Component, { mixins } from 'vue-class-component'
import ExampleMixin from 'ExampleMixin.vue'

@Component({
  components: {
    ExampleMixin
  }
})
export class MyComp extends mixins(ExampleMixin) {
  created () {
    console.log(this.testValue) // The compiler passes}}Copy the code

Passing in multiple mixins is also supported.

Some of the other

Vue.extends() is the most orthodox method in Vue (which is closest to the standard form). With the help of the VScode Vetur plugin, vue.extends () has the advantage of correctly indicating Props on child components:

Classes are special to TypeScript (they can be both types and values). When we use vue-class-component and bind it to a subclass component with $refs, we get the type information exposed on the subcomponent:

Why is an error reported when importing.vue?

The first problem you encounter when using TypeScript in Vue is that you can’t find.vue files in ts files, even if the path you’re writing is fine:

In TypeScript, it only recognizes js/ts/ JSX/TSX files. In order for iT to recognize.vue files, we need to explicitly tell TypeScript that vue files exist and specify that VueConstructor is exported:

declare module '*.vue' {
  import Vue from 'vue'
  export default Vue
}
Copy the code

However, this causes another problem, when we import a nonexistent.vue file, we can also compile:

Yes, that makes sense.

When I try to import existing or non-existing.vue files into the.vue file, I get different results:

File does not exist:

If the file exists:

If the file does not exist, reference Vue’s declaration file. When the file exists, reference the correct file definition.

It’s confusing, and it’s all Vetur’s work.

In this PR, Vetur provides the ability to parse other.vue files to get the correct information, and to read the.d.ts information when the.vue file does not exist.

reference

  • https://github.com/vuejs/vue/pull/5887
  • https://github.com/vuejs/vue/issues/7211
  • https://github.com/vuejs/vue/pull/6856
  • https://github.com/vuejs/vue/pull/5887/files/1092efe6070da2052a8df97a802c9434436eef1e#diff-23d7799dcc9e9be419d28a15348b0 d99
  • https://github.com/Microsoft/TypeScript/blob/8e47c18636da814117071a2640ccf87c5f16fcfd/src/compiler/types.ts#L3563-L3583
  • https://github.com/vuejs/vetur/pull/94