An overview of the

The Vue class component is a library that allows you to create Vue components with class-style syntax. For example, here is a simple counter component written with a Vue class component:

<template>
  <div>
    <button v-on:click="decrement">-</button>
    {{ count }}
    <button v-on:click="increment">+</button>
  </div>
</template>

<script>
import Vue from 'vue'
import Component from 'vue-class-component'

// Define the component in class-style
@Component
export default class Counter extends Vue {
  // Class properties will be component data
  count = 0

  // Methods will be component methods
  increment() {
    this.count++
  }

  decrement() {
    this.count--
  }
}
</script>
Copy the code

As the example shows, you can define Component data and methods with intuitive and standard class syntax by annotating the class with the @Component decorator. You can simply replace the component definition with a class-style component, because it is equivalent to the normal option object style in the component definition.

By defining components in class style, you can not only change the syntax, but also take advantage of ECMAScript language features such as class inheritance and decorators. The Vue class component also provides a Mixin helper for mixin inheritance and a createDecorator function to easily create your own decorators.

You may also want to check out the @Prop and @Watch decorators provided by Vue Property Decorator.

guide

Class Component

@Componen Decorator to make your class a Vue component:

import Vue from 'vue'
import Component from 'vue-class-component'

// HelloWorld class will be a Vue component
@Component
export default class HelloWorld extends Vue {}
Copy the code

Data

Initial data can be declared as class attributes:

<template> <div>{{ message }}</div> </template> <script> import Vue from 'vue' import Component from 'vue-class-component' @Component export default class HelloWorld extends Vue { // Declared as component data message = 'Hello World! ' } </script>Copy the code

The above component renders Hello World! In the div element, message is component data.

Note that if the initial value is undefined, the class attribute will not be responsive, which means that changes to the attribute will not be detected:

import Vue from 'vue'
import Component from 'vue-class-component'

@Component
export default class HelloWorld extends Vue {
  // `message` will not be reactive value
  message = undefined
}
Copy the code

To avoid this, you can use NULL or data hooks instead:

import Vue from 'vue'
import Component from 'vue-class-component'

@Component
export default class HelloWorld extends Vue {
  // `message` will be reactive with `null` value
  message = null

  // See Hooks section for details about `data` hook inside class.
  data() {
    return {
      // `hello` will be reactive as it is declared via `data` hook.
      hello: undefined}}}Copy the code

Methods

Component methods can be declared directly as class stereotype methods:

<template> <button v-on:click="hello">Click</button> </template> <script> import Vue from 'vue' import Component from 'vue-class-component' @Component export default class HelloWorld extends Vue { // Declared as component method hello() {  console.log('Hello World! ') } } </script>Copy the code

Computed properties

Computed properties can be declared as class properties getters/setters:

<template> <input v-model="name"> </template> <script> import Vue from 'vue' import Component from 'vue-class-component'  @Component export default class HelloWorld extends Vue { firstName = 'John' lastName = 'Doe' // Declared as computed property getter get name() { return this.firstName + ' ' + this.lastName } // Declared as computed property setter set name(value) { const splitted = value.split(' ') this.firstName = splitted[0] this.lastName = splitted[1] || '' } } </script>Copy the code

Hooks

Data, Render, and all Vue lifecycle hooks can also be declared directly as class prototype methods, but they cannot be called on the instance itself. Avoid using these reserved names when declaring custom methods.

import Vue from 'vue'
import Component from 'vue-class-component'

@Component
export default class HelloWorld extends Vue {
  // Declare mounted lifecycle hook
  mounted() {
    console.log('mounted')}// Declare render function
  render() {
    return <div>Hello World!</div>}}Copy the code

other

For all other options, pass them to the decorator function:

<template>
  <OtherComponent />
</template>

<script>
import Vue from 'vue'
import Component from 'vue-class-component'
import OtherComponent from './OtherComponent.vue'

@Component({
  // Specify `components` option.
  // See Vue.js docs for all available options:
  // https://vuejs.org/v2/api/#Options-Data
  components: {
    OtherComponent
  }
})
export default class HelloWorld extends Vue {}
</script>
Copy the code

Additional Hooks

If you use some Vue plug-ins, such as the Vue Router, you may need class components to resolve the hooks they provide. In this case, the component. RegisterHooks allow you to registerHooks like this:

// class-component-hooks.js
import Component from 'vue-class-component'

// Register the router hooks with their names
Component.registerHooks([
  'beforeRouteEnter'.'beforeRouteLeave'.'beforeRouteUpdate'
])
Copy the code

Once the hooks are registered, the class component implements them as class prototype methods:

import Vue from 'vue'
import Component from 'vue-class-component'

@Component
export default class HelloWorld extends Vue {
  // The class component now treats beforeRouteEnter,
  // beforeRouteUpdate and beforeRouteLeave as Vue Router hooks
  beforeRouteEnter(to, from, next) {
    console.log('beforeRouteEnter')
    next()
  }

  beforeRouteUpdate(to, from, next) {
    console.log('beforeRouteUpdate')
    next()
  }

  beforeRouteLeave(to, from, next) {
    console.log('beforeRouteLeave')
    next()
  }
}
Copy the code

It is recommended to write this registration code in a separate file because you must register any components before they are defined. You can determine the order of execution by placing the hook registered import statements at the top of the main file:

// main.js

// Make sure to register before importing any components
import './class-component-hooks'

import Vue from 'vue'
import App from './App'

new Vue({
  el: '#app'.render: h= > h(App)
})
Copy the code

Custom constructor

You can extend the functionality of this library by creating your own decorators. The Vue class component provides the createDecorator helper to create custom decorators. CreateDecorator expects a callback function as its first argument, which accepts the following arguments:

  • options: Vue component options object. Changes to this object affect supplied components.
  • key: Applies a decorator property or method key.
  • ParameterIndex ‘: The index of a decorated parameter if a custom decorator is used for a parameter.

An example of creating a log decorator that prints a log message with the method name and passing parameters when the decorated method is called:

// decorators.js
import { createDecorator } from 'vue-class-component'

// Declare Log decorator.
export const Log = createDecorator((options, key) = > {
  // Keep the original method for later.
  const originalMethod = options.methods[key]

  // Wrap the method with the logging logic.
  options.methods[key] = function wrapperMethod(. args) {
    // Print a log.
    console.log(`Invoked: ${key}(`. args,') ')

    // Invoke the original method.
    originalMethod.apply(this, args)
  }
})
Copy the code

Use it as a method decorator:

import Vue from 'vue'
import Component from 'vue-class-component'
import { Log } from './decorators'

@Component
class MyComp extends Vue {
  // It prints a log when `hello` method is invoked.
  @Log
  hello(value) {
    // ...}}Copy the code

In the above code, when the Hello method is called with 42 as an argument, the following log is printed:

Invoked: hello( 42 )
Copy the code

Extend and Mixins

Extend

You can extend existing class components to local class inheritance. Suppose you have the following superclass component:

// super.js
import Vue from 'vue'
import Component from 'vue-class-component'

// Define a super class component
@Component
export default class Super extends Vue {
  superValue = 'Hello'
}
Copy the code

You can extend it by using the native class inheritance syntax:

import Super from './super'
import Component from 'vue-class-component'

// Extending the Super class component
@Component
export default class HelloWorld extends Super {
  created() {
    console.log(this.superValue) // -> Hello}}Copy the code

Note that each parent class must be a class component. In other words, it needs to inherit the Vue constructor and be decorated with the @Component decorator.

Mixins

The Vue class component provides mixins functions that use mixins in a class-style fashion. By using mixins functions, TypeScript can infer mixin types and inherit them on component types.

An example of declaring mixins Hello and World:

// mixins.js
import Vue from 'vue'
import Component from 'vue-class-component'

// You can declare mixins as the same style as components.
@Component
export class Hello extends Vue {
  hello = 'Hello'
}

@Component
export class World extends Vue {
  world = 'World'
}
Copy the code

Use them in class style components:

import Component, { mixins } from 'vue-class-component'
import { Hello, World } from './mixins'

// Use `mixins` helper function instead of `Vue`.
// `mixins` can receive any number of arguments.
@Component
export class HelloWorld extends mixins(Hello.World) {
  created () {
    console.log(this.hello + ' ' + this.world + '! ') // -> Hello World!}}Copy the code

Like parent classes, all mixins must be declared as class components.

Considerations for class components

Vue class components collect class attributes as Vue instance data by instantiating the original constructor underneath. While we can define instance data as we would in the native class way, sometimes we need to know how it works.

Property initializerthis

If you define an arrow function as a class attribute and access it in it, it will not work. This is because when class attributes are initialized, this is just a proxy object for the Vue instance:

import Vue from 'vue'
import Component from 'vue-class-component'

@Component
export default class MyComp extends Vue {
  foo = 123

  // DO NOT do this
  bar = () = > {
    // Does not update the expected property.
    // `this` value is not a Vue instance in fact.
    this.foo = 456}}Copy the code

In this case, you can simply define a method instead of a class property because Vue automatically binds instances:

import Vue from 'vue'
import Component from 'vue-class-component'

@Component
export default class MyComp extends Vue {
  foo = 123

  // DO this
  bar() {
    // Correctly update the expected property.
    this.foo = 456}}Copy the code

Use lifecycle hooks instead of constructors

Since the original constructor is called to collect the initial component data, it is recommended not to declare the constructor yourself:

import Vue from 'vue'
import Component from 'vue-class-component'

@Component
export default class Posts extends Vue {
  posts = []

  // DO NOT do this
  constructor() {
    fetch('/posts.json')
      .then(res= > res.json())
      .then(posts= > {
        this.posts = posts
      })
  }
}
Copy the code

The code above is intended to get the POST list when the component is initialized, but because of the way vUe-class components work, that get will be called unexpectedly twice.

Recommend writing lifecycle hooks such as create instead of constructor:

import Vue from 'vue'
import Component from 'vue-class-component'

@Component
export default class Posts extends Vue {
  posts = []

  // DO this
  created() {
    fetch('/posts.json')
      .then(res= > res.json())
      .then(posts= > {
        this.posts = posts
      })
  }
}
Copy the code

TypeScript guide

Props to define

The Vue class component does not provide a dedicated API for the PROPS definition. However, you can use vue.extendAPI:

<template>
  <div>{{ message }}</div>
</template>

<script lang="ts">
import Vue from 'vue'
import Component from 'vue-class-component'

// Define the props by using Vue's canonical way.
const GreetingProps = Vue.extend({
  props: {
    name: String
  }
})

// Use defined props by extending GreetingProps.
@Component
export default class Greeting extends GreetingProps {
  get message(): string {
    // this.name will be typed
    return 'Hello, ' + this.name
  }
}
</script>

Copy the code

Prop types defined by the vue.extend extension can be used in class components.

If you have a parent component or mixins to extend, use mixins help to combine defined props with them:

<template>
  <div>{{ message }}</div>
</template>

<script lang="ts">
import Vue from 'vue'
import Component, { mixins } from 'vue-class-component'
import Super from './super'

// Define the props by using Vue's canonical way.
const GreetingProps = Vue.extend({
  props: {
    name: String
  }
})

// Use `mixins` helper to combine defined props and a mixin.
@Component
export default class Greeting extends mixins(GreetingProps, Super) {
  get message(): string {
    // this.name will be typed
    return 'Hello, ' + this.name
  }
}
</script>
Copy the code

Attribute declarations

Sometimes you must define component properties and methods from a class component. For example, Vue’s official state management library, Vuex, provides mapGetters and mapActions assistants to map storage to component properties and methods. These helpers need to be used in the component options object.

Even in this case, you can pass Component options to the @Component decorator argument. However, it does not automatically declare properties and methods at the type level when they work at run time.

You need to manually declare their types in class components:

import Vue from 'vue'
import Component from 'vue-class-component'
import { mapGetters, mapActions } from 'vuex'

// Interface of post
import { Post } from './post'

@Component({
  computed: mapGetters([
    'posts'
  ]),

  methods: mapActions([
    'fetchPosts'])})export default class Posts extends Vue {
  // Declare mapped getters and actions on type level.
  // You may need to add `! ` after the property name
  // to avoid compilation error (definite assignment assertion).

  // Type the mapped posts getter.posts! : Post[]// Type the mapped fetchPosts action.fetchPosts! :() = > Promise<void>

  mounted() {
    // Use the mapped getter and action.
    this.fetchPosts().then(() = > {
      console.log(this.posts)
    })
  }
}
Copy the code

$refs extension

The $REFs type of the component is declared as the broadest type to handle all possible REF types. While this is theoretically true, in most cases there is actually only one specific element or component per REF.

You can specify a specific ref type by overriding the $refs type in the class component:

<template> <input ref="input"> </template> <script lang="ts"> import Vue from 'vue' import Component from 'vue-class-component' @Component export default class InputFocus extends Vue { // annotate refs type. // The symbol `! ` (definite assignment assertion) // is needed to get rid of compilation error. $refs! : { input: HTMLInputElement } mounted() { // Use `input` ref without type cast. this.$refs.input.focus() } } </script>Copy the code

You can access input types through $refs without type conversion. The input type is specified on the class component in the example above.

Note that it should be a type comment (using a colon 🙂 rather than a value assignment (=).

Hooks automatically complete

Vue class components provide built-in hook types that automate data, rendering, and other lifecycle hooks in class component declarations. To enable it, you need to import the hook type in vue-class-Component /hooks.

// main.ts
import 'vue-class-component/hooks' // import hooks type to enable auto-complete
import Vue from 'vue'
import App from './App.vue'

new Vue({
  render: h= > h(App)
}).$mount('#app')
Copy the code

If you want it to work with custom hooks, you can add it yourself manually:

import Vue from 'vue'
import { Route, RawLocation } from 'vue-router'

declare module 'vue/types/vue' {
  // Augment component instance type
  interfaceVue { beforeRouteEnter? ( to: Route,from: Route,
      next: (to? : RawLocation |false | ((vm: Vue) => void)) = > void) :voidbeforeRouteLeave? ( to: Route,from: Route,
      next: (to? : RawLocation |false | ((vm: Vue) => void)) = > void) :voidbeforeRouteUpdate? ( to: Route,from: Route,
      next: (to? : RawLocation |false | ((vm: Vue) => void)) = > void) :void}}Copy the code

Annotate the component type in the decorator

There are cases where you want to use Component types on functions in the @Component decorator argument. For example, to access a component method in a monitor handler:

@Component({
  watch: {
    postId(id: string) {
      // To fetch post data when the id is changed.
      this.fetchPost(id) // -> Property 'fetchPost' does not exist on type 'Vue'.}}})class Post extends Vue {
  postId: string

  fetchPost(postId: string) :Promise<void> {
    // ...}}Copy the code

The above code produces a type error indicating that the fetchPost does not exist in the Watch handler. This happens because the type in the @Component decorator argument is based on the Vue type.

To use your own component type (Post in this case), you can annotate the decorator with its type parameter.

// Annotate the decorator with the component type 'Post' so that `this` type in
// the decorator argument becomes 'Post'.
@Component<Post>({
  watch: {
    postId(id: string) {
      this.fetchPost(id) // -> No errors}}})class Post extends Vue {
  postId: string

  fetchPost(postId: string) :Promise<void> {
    // ...}}Copy the code