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