This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.
1 introduction
Vue-class-component (1) is not enough because it has only one class decorator. Only options for data,methods,computed, and nothing for watch, props, etc., will be handled in this library
In this library, a bunch of method decorators are exported
2 look at vue – class – component
It also exports two methods
2.1 mixins
export function mixins(. Ctors: VueClass
[]
) :VueClass<Vue> {
return Vue.extend({
// Mixins are a function that is considered a subclass of Vue
mixins: Ctors,
})
}
Copy the code
2.2 createDecorator
This function is exported in vue-class-component to provide a callback to register options generated after processing the class decorator
export function createDecorator(
factory: (options: ComponentOptions<Vue>, key: string, index: number) = >void
) :VueDecorator {
// The function passed in as a callback for push to __decorators__
// The returned function will be used as a decorator function. It has no return value
return (target: Vue | typeofVue, key? :any, index? :any) = > {
// Target is the prototype object, and target.constructor points to the constructor, which is the @Component decorated class
const Ctor =
typeof target === 'function'
? (target as DecoratedClass)
: (target.constructor as DecoratedClass)
if(! Ctor.__decorators__) { Ctor.__decorators__ = [] }if (typeofindex ! = ='number') {
index = undefined
}
// Save this callback function. Its use was described in the previous article
Ctor.__decorators__.push((options) = > factory(options, key, index))
}
}
Copy the code
3 Vue-property-decorator exports a number of method decorators
3.1 Emit
/ / match ` non-word boundary after ` ` ` with capital letters' aB '= >' B ', 'aB' = > 'B', 'ABC' = > 'B' and 'C', 'ABC DE' = > 'B', 'C' and 'E'
const hyphenateRE = /\B([A-Z])/g
const hyphenate = (str: string) = > str.replace(hyphenateRE, '- $1').toLowerCase()
/**
* decorator of an event-emitter function
* @param event The name of the event
* @return MethodDecorator* /
export function Emit(event? :string) {
return function (_target: Vue, propertyKey: string, descriptor: any) {
// Hump to line
const key = hyphenate(propertyKey)
// Save the old function
const original = descriptor.value
// Assign a new function
descriptor.value = function emitter(. args:any[]) {
const emit = (returnValue: any) = > {
// The name of the event triggered
const emitName = event || key
If the old function returns a value, place it first
returnValue ?? args.unshift(returnValue)
// Trigger the event
this.$emit(emitName, ... args) }// Call the old function
const returnValue: any = original.apply(this, args)
// If the old function returns Promise
if (isPromise(returnValue)) {
returnValue.then(emit)
} else {
emit(returnValue)
}
return returnValue
}
}
}
function isPromise(obj: any) :obj is Promise<any> {
return obj instanceof Promise || (obj && typeof obj.then === 'function')}Copy the code
usage
<! -- Parent component -->
<template>
<div>
<Emit @click="clickEmit" @fn="clickEmit"></Emit>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import Emit from './emit.vue'
@Component({
components: {
Emit,
},
})
class Demo extends Vue {
clickEmit(. args) {
console.log(args)
}
}
export default Demo
</script>
Copy the code
<! -- Subcomponent -->
<template>
<div class="home">
<div @click="Emit (' parameter passed later ')">Emit decorator name</div>
<div @click="Fn (' passed argument ')">Emit function naming</div>
</div>
</template>
<script lang="ts">
import { Component, Vue, Emit } from 'vue-property-decorator'
@Component
export default class Home extends Vue {
@Emit('click')
emit() {
console.log('Emit decorator name')
return 'Previously passed parameter'
}
@Emit()
fn() {
console.log('Emit function named, fires FN, returns no value')}}</script>
Copy the code
3.1.1 contrast
// How to use decorator
import { Component, Vue, Emit } from 'vue-property-decorator'
@Component
export default class Demo extends Vue {
@Emit('emit-demo')
emitFn() {
// ...
console.log('emitFn')
// ...}}Copy the code
/ / options
export default {
methods: {
oldEmitFn() {
// ...
console.log('emitFn')
// ...
},
emitFn(. args) {
const returnValue = this.oldEmitFn(... args)const emit = (returnValue) = > {
returnValue ?? args.unshift(returnValue)
// The only difference is that @emit() uses the event name if it is passed, the function name otherwise
const emitName = 'emit-demo'
this.$emit(emitName, ... args) }if (isPromise(returnValue)) {
returnValue.then(emit)
} else {
emit(returnValue)
}
},
},
}
Copy the code
3.2 the Model
import Vue, { PropOptions } from 'vue2'
import { createDecorator } from 'vue-class-component'
import { Constructor } from 'vue2/types/options'
/**
* decorator of model
* @param event event name
* @param options options
* @return PropertyDecorator* /
export function Model(event? :string,
options: PropOptions | Constructor[] | Constructor = {}
) {
//
return (target: Vue, key: string) = > {
// ==== decorator function starts ===
const factory = (componentOptions, k) = > {
// Modify props and model
componentOptions.props || ((componentOptions.props = {}) as any)
componentOptions.props[k] = options
componentOptions.model = { prop: k, event: event || k }
}
createDecorator(factory)(target, key)
// ==== decorator function end ===}}Copy the code
3.2.1 contrast
// How to use decorator
import { Component, Vue, Model } from 'vue-property-decorator'
@Component
export default class Demo extends Vue {
@Emit('event', { type: Object })
eventName
}
Copy the code
/ / options
export default {
props: {
event: { type: Object}},model: {
prop: 'eventName'.event: 'event',}}Copy the code
3.3 ModelSync
import Vue, { PropOptions } from 'vue2'
import { createDecorator } from 'vue-class-component'
import { Constructor } from 'vue2/types/options'
/**
* decorator of synced model and prop
* @param propName the name to interface with from outside, must be different from decorated property
* @param event event name
* @param options options
* @return PropertyDecorator* /
export function ModelSync(
propName: string, event? :string,
options: PropOptions | Constructor[] | Constructor = {}
) {
return (target: Vue, key: string) = > {
const factory = (componentOptions, k) = > {
componentOptions.props || ((componentOptions.props = {}) as any)
componentOptions.props[propName] = options
componentOptions.model = { prop: propName, event: event || k }
// Pass in one more propName than Model and do a bit more computations
componentOptions.computed || (componentOptions.computed = {})
componentOptions.computed[k] = {
get() {
return (this as any)[propName]
},
set(value: any) {
this.$emit(event, value)
},
}
}
createDecorator(factory)(target, key)
}
}
Copy the code
3.3.1 contrast
// The decorator
import { Component, Vue, ModelSync } from 'vue-property-decorator'
@Component
export default class Demo extends Vue {
@ModelSync('propName'.'event', { type: Object}) eventName! :any
}
Copy the code
/ / options
export default {
props: {
propName: { type: Object}},model: {
prop: 'eventName'.event: 'event',},computed: {
eventName: {
get() {
return this.propsName
},
set(value) {
this.$emit('event', value)
},
},
},
}
Copy the code
3.4 Prop
import Vue, { PropOptions } from 'vue2'
import { createDecorator } from 'vue-class-component'
import { Constructor } from 'vue2/types/options'
/**
* decorator of a prop
* @param options the options for the prop
* @return PropertyDecorator | void
*/
export function Prop(options: PropOptions | Constructor[] | Constructor = {}) {
return (target: Vue, key: string) = > {
createDecorator((componentOptions, k) = > {
componentOptions.props || ((componentOptions.props = {}) as any)
// Simply add an attribute to props, where k is the name of the Prop method and options is the parameter of Prop
componentOptions.props[k] = options
})(target, key)
}
}
Copy the code
3.4.1 track contrast
// The decorator
import { Component, Vue, Prop } from 'vue-property-decorator'
@Component
export default class Demo extends Vue {
@Prop({ type: Object}) propName! :any
}
Copy the code
/ / options
export default {
props: {
propName: { type: Object}},}Copy the code
3.5 PropSync
import Vue, { PropOptions } from 'vue2'
import { createDecorator } from 'vue-class-component'
import { Constructor } from 'vue2/types/options'
/**
* decorator of a synced prop
* @param propName the name to interface with from outside, must be different from decorated property
* @param options the options for the synced prop
* @return PropertyDecorator | void
*/
export function PropSync(
propName: string,
options: PropOptions | Constructor[] | Constructor = {}
) {
return (target: Vue, key: string) = > {
createDecorator((componentOptions, k) = > {
componentOptions.props || (componentOptions.props = {} as any)
componentOptions.props[propName] = options
componentOptions.computed || (componentOptions.computed = {})
componentOptions.computed[k] = {
get() {
return (this as any)[propName]
},
set(this: Vue, value: any) {
this.$emit(`update:${propName}`, value)
},
}
})(target, key)
}
}
Copy the code
3.5.1 track of contrast
// The decorator
import { Component, Vue, PropSync } from 'vue-property-decorator'
@Component
export default class Demo extends Vue {
@PropSync('propName', { type: Object}) computedKey! :any
}
Copy the code
/ / options
export default {
props: {
propName: { type: Object}},computed: {
computedKey: {
get() {
return this.propName
},
set(value) {
this.$emit('update:propName', value)
},
},
},
}
Copy the code
3.6 Ref
import Vue from 'vue2'
import { createDecorator } from 'vue-class-component'
/**
* decorator of a ref prop
* @param refKey the ref key defined in template
*/
export function Ref(refKey? :string) {
return createDecorator((options, key) = > {
options.computed = options.computed || {}
options.computed[key] = {
cache: false.get(this: Vue) {
return this.$refs[refKey || key]
},
}
})
}
Copy the code
3.6.1 contrast
// The decorator
import { Component, Vue, Ref } from 'vue-property-decorator'
@Component
export default class Demo extends Vue {
@Ref('refKey') key! :any
}
Copy the code
/ / options
export default {
computed: {
key: {
cache: false.get() {
return this.$refs.refKey
},
},
},
}
Copy the code
3.7 VModel
import Vue, { PropOptions } from 'vue2'
import { createDecorator } from 'vue-class-component'
/**
* decorator for capturings v-model binding to component
* @param options the options for the prop
*/
export function VModel(options: PropOptions = {}) {
const valueKey: string = 'value'
return createDecorator((componentOptions, key) = > {
componentOptions.props || ((componentOptions.props = {}) as any)
componentOptions.props[valueKey] = options
componentOptions.computed || (componentOptions.computed = {})
componentOptions.computed[key] = {
get() {
return (this as any)[valueKey]
},
set(this: Vue, value: any) {
this.$emit('input', value)
},
}
})
}
Copy the code
3.7.1 contrast
// The decorator
import { Component, Vue, VModel } from 'vue-property-decorator'
@Component
export default class Demo extends Vue {
@VModel({ type: Object}) computedKey! :any
}
Copy the code
/ / options
export default {
props: {
value: {
type: Object,}},computed: {
computedKey: {
get() {
return this.value
},
set(value) {
this.$emit('input', value)
},
},
},
}
Copy the code
3.8 Watch
import { WatchOptions } from 'vue2'
import { createDecorator } from 'vue-class-component'
/**
* decorator of a watch function
* @param path the path or the expression to observe
* @param WatchOption
* @return MethodDecorator* /
export function Watch(path: string, options: WatchOptions = {}) {
const { deep = false, immediate = false } = options
return createDecorator((componentOptions, handler) = > {
if (typeofcomponentOptions.watch ! = ='object') {
componentOptions.watch = Object.create(null)}const watch: any = componentOptions.watch
if (typeof watch[path] === 'object'&&!Array.isArray(watch[path])) {
watch[path] = [watch[path]]
} else if (typeof watch[path] === 'undefined') {
watch[path] = []
}
watch[path].push({ handler, deep, immediate })
})
}
Copy the code
3.8.1 contrast
// The decorator
import { Component, Vue, Watch } from 'vue-property-decorator'
@Component
export default class Demo extends Vue {
@Watch('path', { deep: true })
watchFn() {
// ...}}Copy the code
/ / options
export default {
watch: {
path: {
...{ deep: true },
handler: this.watchFn,
},
},
methods: {
watchFn() {
// ...}},}Copy the code
4 summarizes
Prop and Watch are commonly used. For others, it is better to use options directly
It just helps us get better type derivation and can get rid of the options jump up and down
For example, if you’re looking at nuggets and you have no right side navigation, you have to scroll up and down. The class notation is very good for type derivation, and in the editor, you can quickly locate them
Imagine if we didn’t have this navigation
The last 5
Next plan to write modular, please look forward to, feel this article can be changed to your inspiration, hope to give a like, comment, favorites, attention…
As usual, I enclose a few previous posts
- Vue2 source code analysis nextTick
- Code snippet JS flow limiting scheduler
- Linked Lists of Data Structures and Algorithms (1)
- Vue2 source code parsing event system $on
- Vue2 – Global API source code analysis
- Vue-class-component (1)
- Prototype of js native syntax,protoAnd the constructor
- The inheritance and implementation of js native syntax