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

  1. Vue2 source code analysis nextTick
  2. Code snippet JS flow limiting scheduler
  3. Linked Lists of Data Structures and Algorithms (1)
  4. Vue2 source code parsing event system $on
  5. Vue2 – Global API source code analysis
  6. Vue-class-component (1)
  7. Prototype of js native syntax,protoAnd the constructor
  8. The inheritance and implementation of js native syntax