[Vue]vue2.6 detailed explanation of Vue class component and Vue property decorator using TS

vue2.6 middle pair typescript support
Vue CLI 3 can generate new projects using TypeScript.

# 1. If Vue CLI is not installed, install it first
cnpm install --global @vue/cli

# 2. Create a new project and select the "Manually select features" option
vue create my-project-name

1. vue-class-component

vue-class-component It is the official library of vue. Its function is to write components in the way of classes. This way of writing allows you to The js domain structure of vue file is more flat, and enables vue components to use advanced features such as inheritance and blending.

vue2.x is not friendly to TS support, so vue2 The integration of X and TS usually needs to be based on Vue class component (officially recommended) and written by class based components.

Vue class Component (hereinafter referred to as Component) brings many conveniences:

  • The methods hook can directly write the methods of class
  • The computed attribute can be obtained directly through get
  • Initialize the attribute that data can declare as class
  • Everything else can be put into the Component decorator

Official documents: https://class-component.vuejs.org/

Basic examples are as follows:

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

// @The Component modifier indicates that this class is a Vue Component
@Component({
  // All component options can be placed here
  template: '<button @click="onClick">Click!</button>'
})
export default class MyComponent extends Vue {
  // The initial data can be directly declared as the property of the instance
  message: string = 'Hello!'

  // Component methods can also be declared directly as instance methods
  onClick (): void {
    window.alert(this.message)
  }
}

2. vue-property-decorator

vue-property-decorator It is an unofficial library and a good supplement to Vue class component.
It allows some attributes and methods of vue to be written into the class of vue component instance through the writing method of modifier.
For example, @ Prop @Watch @Emit

2.0 installation and download

# Install the official plug-in of vue
npm install vue-class-component vue-property-decorator --save-dev

# TS loader typescript must be installed. I believe you will install it in the future
npm i ts-loader typescript tslint tslint-loader tslint-config-standard --save-dev

The general functions of these libraries can be introduced as needed:

  • vue-class-component : enhance Vue components and use TypeScript / decorator to enhance Vue components
  • vue-property-decorator : enhance more decorators combining Vue features on Vue class component
  • ts-loader : TypeScript provides TS loader for webpack, which is actually for webpack to recognize ts .tsx file
  • tslint-loader Follow tslint : I think you'll be there, too ts .tsx file constraint code format (equivalent to eslint)
  • tslint-config-standard : tslint configure standard style constraints

Note: you can refer to this repo Vue typescript starter scaffold (follow the article step by step). This project started with the idea of adding strong types to vue. The purpose is to provide a scaffold for quick start and start. It is mainly based on the use of vue + typescript vue single file development scaffold, supporting jsx

2.1 @Component (inherited from Vue class component)

@The Component decorator can receive an object as a parameter. It can declare components, filters, directives, beforeRouteLeave, beforeRouteEnter and other options that do not provide decorators in the object. It can also declare calculated, watch and so on.

@Component({
  components: {
    HellowWordComponent,
  },
  beforeRouteLeave(to: any, from: any, next: any) {
    console.log('beforeRouteLeave');
    next();
  },
  beforeRouteEnter(to: any, from: any, next: any) {
    console.log('beforeRouteLeave');
    next();
  },
})

2.2 Mixins (inherited from Vue class component)

When using Vue for development, we often use mixing. Combined with TypeScript, we have two methods of mixins

One is provided by Vue class component:

//Define the class mixins to be mixed ts
import Vue from 'vue';
import  Component  from 'vue-class-component';

@Component  // Be sure to decorate with Component
export default class myMixins extends Vue {
    value: string = "Hello"
}
// introduce
import  Component  {mixins}  from 'vue-class-component';
import myMixins from 'mixins.ts';

@Component
export class myComponent extends mixins(myMixins) {
    // Direct extensions myminxins can also operate normally
    created(){
          console.log(this.value) // => Hello
    }
}

The second way is to mix in @ Component:
Let's transform mixins TS, define vue/type/vue module and implement Vue interface

// mixins.ts
import { Vue, Component } from 'vue-property-decorator';


declare module 'vue/types/vue' {
    interface Vue {
        value: string;
    }
}

@Component
export default class myMixins extends Vue {
    value: string = 'Hello'
}

Mix in

import { Vue, Component, Prop } from 'vue-property-decorator';
import myMixins from '@static/js/mixins';

@Component({
    mixins: [myMixins]
})
export default class myComponent extends Vue{
    created(){
        console.log(this.value) // => Hello
    }
}

Summary:
The difference between the two methods is that if the vue/type/vue module is not defined when defining mixins, the mixins must be inherited when mixing in;
If vue/type/vue module is defined, mixins can be directly introduced into @ Component during mixing

2.3 @Prop

@The Prop decorator receives a parameter, which can be written in three ways:

  • Specify the type of prolean, constru, String, etc;
  • Constructor [], specifies the optional type of prop;
  • PropOptions, you can use the following options: type, default, required, validator.

Note: you need to add undefined type after ts type of attribute; Or add!, Represents non null and non undefined assertions, otherwise the compiler will give an error prompt.

// Parent component:
<template>
  <div class="Props">
    <PropComponent :name="name" :age="age" :sex="sex"></PropComponent>
  </div>
</template>
 
<script lang="ts">
	import {Component, Vue,} from 'vue-property-decorator';
	import PropComponent from '@/components/PropComponent.vue';
	 
	@Component({
	  components: {PropComponent,},
	})
	export default class PropsPage extends Vue {
	  private name = 'Zhang San';
	  private age = 1;
	  private sex = 'nan';
	}
</script>



// Sub components:
<template>
  <div class="hello">
    name: {{name}} | age: {{age}} | sex: {{sex}}
  </div>
</template>
 
<script lang="ts">
	import {Component, Vue, Prop} from 'vue-property-decorator';
	 
	@Component
	export default class PropComponent extends Vue {
	   @Prop(String) readonly name!: string | undefined;
	   @Prop({ default: 30, type: Number }) private age!: number;
	   @Prop([String, Boolean]) private sex!: string | boolean;
	}
</script>

2.4 @PropSync

@The usage of PropSync decorator is similar to @ prop. The difference between them is:

  • @The PropSync decorator receives two parameters:
    propName: string indicates the property name passed from the parent component;
    options: Constructor | Constructor[] | PropOptions is consistent with the first parameter of @ Prop;
  • @PropSync generates a new calculated property.
    Note: when using PropSync, you should cooperate with the parent component Used by sync
// Parent component
<template>
  <div class="PropSync">
    <h1>Parent component</h1>
    like:{{like}}
    <hr/>
    <PropSyncComponent :like.sync="like"></PropSyncComponent>
  </div>
</template>
 
<script lang='ts'>
import { Vue, Component } from 'vue-property-decorator';
import PropSyncComponent from '@/components/PropSyncComponent.vue';
 
@Component({components: { PropSyncComponent },})
export default class PropSyncPage extends Vue {
  private like = 'Parent component like';
}
</script>
 

// Subcomponents
<template>
  <div class="hello">
    <h1>Subcomponents:</h1>
    <h2>syncedlike:{{ syncedlike }}</h2>
    <button @click="editLike()">modify like</button>
  </div>
</template>
 
<script lang="ts">
import { Component, Prop, Vue, PropSync,} from 'vue-property-decorator';
 
@Component
export default class PropSyncComponent extends Vue {
  @PropSync('like', { type: String }) syncedlike!: string; // It is used to realize the two-way binding of components. Child components can change the value of the parent component
 
  editLike(): void {
    this.syncedlike = 'Sub component modified syncedlike!'; // Two way binding. Changing syncedlike will change the like of the parent component
  }
}
</script>

2.5 @Model

model allows a custom component to be used v-model Customize prop and event when.
@The Model decorator allows us to customize the v-model on a component and receive two parameters:

  • event: string event name.
  • options: Constructor | Constructor[] | PropOptions is consistent with the first parameter of @ Prop.
    The following is an example of Vue's official website
Vue.component('my-checkbox', {
  model: {
    prop: 'checked',
    event: 'change'
  },
  props: {
    // this allows using the `value` prop for a different purpose
    value: String,
    // use `checked` as the prop which take the place of `value`
    checked: {
      type: Number,
      default: 0
    }
  },
  // ...
})
<my-checkbox v-model="foo" value="some value"></my-checkbox>

The above code is equivalent to:

<my-checkbox
  :checked="foo"
  @change="val => { foo = val }"
  value="some value">
</my-checkbox>

That is, foo bidirectional binding refers to the checked of the component, and the event triggering the bidirectional binding value is change
Use @ Model provided by Vue property decorator to transform the above example:

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

@Component
export class myCheck extends Vue{
   @Model ('change', {type: Boolean})  checked!: boolean;
}

In summary, @ Model() receives two parameters. The first is the event value and the second is the type description of Prop. Similar to @ Prop, the type here should be JS The following is the Prop and the type description under TS.

// Parent component
<template>
  <div class="Model">
    <ModelComponent v-model="fooTs" value="some value"></ModelComponent>
    <div>Parent component app : {{fooTs}}</div>
  </div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import ModelComponent from '@/components/ModelComponent.vue';
 
@Component({ components: {ModelComponent} })
export default class ModelPage extends Vue {
  private fooTs = 'App Foo!';
}
</script>
 
// Subcomponents
<template>
  <div class="hello">
    Subcomponents:<input type="text" :value="checked" @input="inputHandle($event)"/>
  </div>
</template>
 
<script lang="ts">
import {Component, Vue, Model,} from 'vue-property-decorator';
 
@Component
export default class ModelComponent extends Vue {
   @Model('change', { type: String }) readonly checked!: string
 
   public inputHandle(that: any): void {
     this.$emit('change', that.target.value); // We'll talk about @ emit later. Let's use this first$ Emit instead
   }
}
</script>

2.6 @ModelSync

2.7 @Watch

@The Watch decorator receives two parameters:

  • path: string the name of the property being listened on;
  • options?: WatchOptions={} options can contain two properties:
    • immediate?: Whether to call the callback function immediately after the start of Boolean listening;
    • deep?: Whether to call the callback function when the properties of the object being listened on by Boolean are changed;

Note: this occurs after the beforeCreate hook and before the created hook

<template>
  <div class="PropSync">
    <h1>child:{{child}}</h1>
    <input type="text" v-model="child"/>
  </div>
</template>
 
<script lang="ts">
import { Vue, Watch, Component } from 'vue-property-decorator';
 
@Component
export default class WatchPage extends Vue {
  private child = '';
 
  // deep: true: listen to the event depth once
  @Watch('child', , {immediate: true, deep: true})
  onChildChanged(newValue: string, oldValue: string) {
    console.log(newValue);
    console.log(oldValue);
  }
}
</script>

2.8 @Emit

  • @The emit decorator receives an optional parameter, which is the first parameter of $emit and acts as the event name. If this parameter is not provided, $emit will convert the camelCase of the callback function name to kebab case and take it as the event name;
  • @Emit will take the return value of the callback function as the second parameter. If the return value is a Promise object, $emit will be triggered after the Promise object is marked as resolved;
  • @The parameters of the callback function of emit will be placed after its return value and used together by $emit as parameters.
// Parent component
<template>
  <div class="">
    click emit Gets the name of the subcomponent<br/>
    full name:{{emitData.name}}
    <hr/>
    <EmitComponent sex='female' @add-to-count="returnPersons" @delemit="delemit"></EmitComponent>
  </div>
</template>
 
<script lang="ts">
import { Vue, Component } from 'vue-property-decorator';
import EmitComponent from '@/components/EmitComponent.vue';
 
@Component({
  components: { EmitComponent },
})
export default class EmitPage extends Vue {
  private emitData = { name: 'I don't have a name yet' };
 
  returnPersons(data: any) {
    this.emitData = data;
  }
 
  delemit(event: MouseEvent) {
    console.log(this.emitData);
    console.log(event);
  }
}
</script>
 
// Subcomponents
<template>
  <div class="hello">
    Subcomponents:
    <div v-if="person">
      full name:{{person.name}}<br/>
      Age:{{person.age}}<br/>
      Gender:{{person.sex}}<br/>
    </div>
    <button @click="addToCount(person)">click emit</button>
    <button @click="delToCount($event)">click del emit</button>
  </div>
</template>
 
<script lang="ts">
import {
  Component, Vue, Prop, Emit,
} from 'vue-property-decorator';
 
type Person = {name: string; age: number; sex: string };
 
@Component
export default class PropComponent extends Vue {
  private name: string | undefined;
 
  private age: number | undefined;
 
  private person: Person = { name: 'I'm Zhang San of the sub assembly', age: 1, sex: 'male' };
 
  @Prop(String) readonly sex: string | undefined;
 
  @Emit('delemit') private delEmitClick(event: MouseEvent) {}
 
  @Emit() // If you do not use the default function name set here, do not use the following
  addToCount(p: Person) { // If there are capital letters in the naming here, you need to separate @ add to count with a horizontal line
    return this.person; // If return is not used here, the parameter p in parentheses will be used by default;
  }
 
  delToCount(event: MouseEvent) {
    this.delEmitClick(event);
  }
}
</script>

2.9 @Provice

@Provide(key?: string | symbol)

2.10 @Inject

@Inject(options?: { from?: InjectKey, default?: any } | InjectKey)

2.11 @ProvideReactive

@ProvideReactive(key?: string | symbol)

2.12 @InjectReactive

@InjectReactive(options?: { from?: InjectKey, default?: any } | InjectKey)

Provide / inject decorators. The key can be of string or symbol type.

  • Similarities: the data provided by Provide/ProvideReactive can be obtained by using Inject/InjectReactive in internal components.
  • Difference: if the value of ProvideReactive provided is modified by the parent component, the child component can use InjectReactive to capture this modification.

Code example:

<!--Outermost component-->
<template>
  <div class="">
    <H3>ProvideInjectPage page</H3>
    <div>
      stay ProvideInjectPage Page usage Provide,ProvideReactive Define data,unwanted props Transfer data
      Then grandpa set his parents up,Parents trap sons,Son sets grandson,Finally, it is obtained in the grandson component ProvideInjectPage
      The information inside
    </div>
    <hr/>
    <provideGrandpa></provideGrandpa> <!--Grandpa component-->
  </div>
</template>
 
<script lang="ts">
import {
  Vue, Component, Provide, ProvideReactive,
} from 'vue-property-decorator';
import provideGrandpa from '@/components/ProvideGParentComponent.vue';
 
@Component({
  components: { provideGrandpa },
})
export default class ProvideInjectPage extends Vue {
  @Provide() foo = Symbol('fooaaa');
 
  @ProvideReactive() fooReactive = 'fooReactive';
 
  @ProvideReactive('1') fooReactiveKey1 = 'fooReactiveKey1';
 
  @ProvideReactive('2') fooReactiveKey2 = 'fooReactiveKey2';
 
  created() {
    this.foo = Symbol('fooaaa111');
    this.fooReactive = 'fooReactive111';
    this.fooReactiveKey1 = 'fooReactiveKey111';
    this.fooReactiveKey2 = 'fooReactiveKey222';
  }
}
</script>
 
 <!--provideGrandpa Call parent component-->
<template>
  <div class="hello">
    <ProvideParentComponent></ProvideParentComponent>
  </div>
</template>
 
<!--ProvideParentComponent Call son component-->
<template>
  <div class="hello">
    <ProvideSonComponent></ProvideSonComponent>
  </div>
</template>
 
<!--ProvideSonComponent Call grandchild component-->
<template>
  <div class="hello">
    <ProvideGSonComponent></ProvideGSonComponent>
  </div>
</template>
 
 
<!--Grandson assembly<ProvideGSonComponent>,
After multiple references,Used in grandchildren components Inject You can get the outermost component provide My data-->
<template>
  <div class="hello">
    <h3>Grandson's components</h3>
    Grandpa, it's in the assembly foo:{{foo.description}}<br/>
    Grandpa, it's in the assembly fooReactive:{{fooReactive}}<br/>
    Grandpa, it's in the assembly fooReactiveKey1:{{fooReactiveKey1}}<br/>
    Grandpa, it's in the assembly fooReactiveKey2:{{fooReactiveKey2}}
    <span style="padding-left:30px;">=> fooReactiveKey2 Not some key So I can't get it</span>
  </div>
</template>
 
<script lang="ts">
import {
  Component, Vue, Inject, InjectReactive,
} from 'vue-property-decorator';
 
@Component
export default class ProvideGSonComponent extends Vue {
  @Inject() readonly foo!: string;
 
  @InjectReactive() fooReactive!: string;
 
  @InjectReactive('1') fooReactiveKey1!: string;
 
  @InjectReactive() fooReactiveKey2!: string;
}
</script>

2.13 @Ref

@The Ref decorator receives an optional parameter to point to the reference information of the element or subcomponent. If this parameter is not provided, the attribute name after the decorator will be used as the parameter

<template>
  <div class="PropSync">
    <button @click="getRef()" ref="aButton">obtain ref</button>
    <RefComponent name="names" ref="RefComponent"></RefComponent>
  </div>
</template>
 
<script lang="ts">
import { Vue, Component, Ref } from 'vue-property-decorator';
import RefComponent from '@/components/RefComponent.vue';
 
@Component({
  components: { RefComponent },
})
export default class RefPage extends Vue {
  @Ref('RefComponent') readonly RefC!: RefComponent;
  @Ref('aButton') readonly ref!: HTMLButtonElement;
  getRef() {
    console.log(this.RefC);
    console.log(this.ref);
  }
}
</script>

2.14 @VModel

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

@Component
export default class YourComponent extends Vue {
  @VModel({ type: String }) name!: string
}

Equivalent to

export default {
  props: {
    value: {
      type: String,
    },
  },
  computed: {
    name: {
      get() {
        return this.value
      },
      set(value) {
        this.$emit('input', value)
      },
    },
  },
}

3. Difference and connection

(1) Vue class component is an official product; Vue property decorator is produced by the community;

(2) Vue class Component provides Vue, Component, etc;

(3) Vue property decorator deeply relies on Vue class component and expands more operators: @ Prop, @ Emit, @ Inject, @ Model, @ Provide, @ Watch;

4. Example code

The vue property decorator is normally introduced during development, and the vue code is written after the introduction

<!-- vue-property-decorator grammar vue-class-component grammar TS grammar -->
<template>
  <div class="hello">
    <!-- props Data -->
    <h1>{{ msg }}</h1>
    <!-- Use of variables -->
    <p>{{ dataMsg }} -- Initial value undefined: {{testMsg}}</p>
    <!-- Use of components -->
    <About/>
    <!-- Event call -->
    <button v-on:click="hello">Click Order me</button>
    <!-- ref use -->
    <input ref="input">
    <p>Digital type of data transmitted{{count}}----The name string must have a value:{{name}}----Boolean:{{flag}}</p>
    <p>Calculation properties:{{newMsg}}</p>
  </div>
</template>

<script lang="ts">
import { Component, Prop, Vue, Watch } from "vue-property-decorator";
import About from "@/views/About.vue"; // Import components
import { mapGetters, mapActions } from "vuex" // vuex module

// Follow the steps below to use the method api: https://www.npmjs.com/package/vue-property-decorator

/* ts Special symbol usage
1. Use in attribute or parameter?: Indicates that the attribute or parameter is optional
2. Used in attribute or parameter!: Indicates forced parsing (tell the typescript compiler that there must be a value here), which is often used in @ Prop in Vue decorator
3. Use after variable!: Indicates that type inference excludes null and undefined */

// Decorator usage
@Component({
  // Register components
  components: {
    About
  },
  beforeRouteLeave(to: any, from: any, next: any) {
    console.log('beforeRouteLeave');
    next();
  },
  beforeRouteEnter(to: any, from: any, next: any) {
    console.log('beforeRouteLeave');
    next();
  },
  // computed: mapGetters([
  //   'xx'
  // ]),
  // methods: mapActions([
  //   'xx'
  // ])
})

// Prop is equivalent to
// props: {
//     msg: {
//       type: String
//     }
//     count: {
//       type: Number,
//     },
//     name: {
//       default: 'default value',
//     },
//     addr: {
//       type: [String, Boolean],
//     },
// },

export default class HelloWorld extends Vue {
  @Prop() private msg!: string;
  @Prop(Number) readonly count: number | undefined
  @Prop({ default: 'default value' }) readonly name!: string
  @Prop([String, Boolean]) readonly flag: string | boolean | undefined
  $refs!: {
    input: HTMLInputElement
  }
  dataMsg = "This is equivalent to vue + js of data The data inside"; // Please note that if the initial value is directly declared as undefined here, the class attribute will not be reactive. To avoid this situation, you can use null, value or datahook instead (as follows);
  personObj = {
    name: 'ts',
    age: 1
  }
  
  created() {
    console.log("created-1")
  }
  // Declare mounted lifecycle hook lifecycle
  mounted() {
    console.log("mounted-2")
    this.$refs.input.focus()
  }
  
  // data hook initialization value
  data(){
    return {
      testMsg: undefined
    }
  }
  
  // deep: true: listen to the event depth once
  @Watch('personObj.name', {immediate: true, deep: true})
  onChangeValue(val:string){
    console.log('watch Data inside:' + val) // ts
  }
  
  // Calculate attributes - this requires getter s
  /* For the calculation attribute in Vue, we only need to define the calculation attribute name as a function and add the get keyword before the function
	Originally, each calculated attribute in Vue's calculated has become a function of adding get to the prefix */ 
  get newMsg(){
    return 'Hello Ts' + this.dataMsg
  }
  
  // Pop up Hello World!
  hello() {
    alert("Hello World!")
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">

</style>

Now vue3 X has been released, which is very friendly to TS, so vue3.0 is used X is not needed for the time being.

https://juejin.cn/post/6844903506676023310
https://vvbug.blog.csdn.net/article/details/106753517
https://blog.csdn.net/vv_bug/article/details/106758835
https://segmentfault.com/a/1190000011744210
https://segmentfault.com/a/1190000011878086?utm_source=sf-similar-article
https://www.cnblogs.com/lhl66/p/13577549.html
https://segmentfault.com/a/1190000018720570

Keywords: Javascript Front-end TypeScript Vue Vue.js

Added by drorgo on Sun, 06 Mar 2022 09:18:08 +0200