Catalogue of 0.

Get an in-depth understanding of the Typescript(I) -ES syntax class properties and decorators in Vue Understand Typescript in Vue (3)- Use Typescript in the vue2 project

1. An overview of the

Following up on the previous article, we defined a component in Typescript that can be defined in the following class style

<template>
    <div>
    	<button @click="handleClick">{{count}}</button>
        <hello-world></hello-world>
    </div>
</template>
<script>
import Vue from 'vue'
import Component from 'vue-class-component'    
import HelloWorld = from './HelloWorld.vue'
    
@Component({
    components: {
        'hello-world': HelloWorld
    }    
})
export default class Counter extends Vue {
    count = 0   
    created(){
      	this.count = 1  
    }
    handleClick(){
        this.count++
    }
}
</script>
Copy the code

This code uses the @Component modifier, which comes from the vue-class-Component project. What does it do from its source code point of view? Here is the address of the project at https://github.com/vuejs/vue-class-component

2. Index. ts source preview

2.1 Entry file index.js

Take a look at the project entry file SRC /index.ts

import Vue, { ComponentOptions } from 'vue'
import { VueClass } from './declarations'
import { componentFactory, $internalHooks } from './component'

export { createDecorator, VueDecorator, mixins } from './util'

function Component <V extends Vue> (options: ComponentOptions<V> & ThisType<V>).VC extends VueClass<V> > (target: VC) = >VC
function Component <VC extends VueClass<Vue> > (target: VC) :VC
function Component (options: ComponentOptions<Vue> | VueClass<Vue>) :any {
  if (typeof options === 'function') {
    return componentFactory(options)}return function (Component: VueClass<Vue>) {// Handle the components defined by the class stylereturn componentFactory(Component, options)}}Component.registerHooks = function registerHooks (keys: string[]) :void {
  $internalHooks.push(. keys)}export default Component
Copy the code

Analysis of the above code, do not need to understand all the details of the code, need to grasp the main points, understand two points:

(1) ComponentMethod definition

(2) componentFactoryMethods effect

That is to understand the following statement

// (1)Component method definition
function Component <V extends Vue> (options: ComponentOptions<V> & ThisType<V>).VC extends VueClass<V> > (target: VC) = >VC
function Component <VC extends VueClass<Vue> > (target: VC) :VC
function Component (options: ComponentOptions<Vue> | VueClass<Vue>) :any {
  if (typeof options === 'function') {/ / (2)componentFactoryFunction of methodreturn componentFactory(options)}return function (Component: VueClass<Vue>) {/ / (2)componentFactoryFunction of methodreturn componentFactory(Component, options)}}Copy the code

To understand the above statement, we need to understand Typescript syntax. The syntax of these two parts is explained below

3. The Typescript syntax

3.1 Method overloading

First of all, method overloading means that a method with the same name can be defined, and different methods can be called according to different parameters. However, method overloading is not supported in native javascript, such as the following statement

function fn (a) { // The first method takes 1 argument
    console.log(a)
}
function fn (a, b) { // The second method, overriding the first method, takes two arguments
    console.log(a,b)
}

fn(1) // Always call the second method
Copy the code

If you want to execute different results depending on the parameters and combine two methods into one, you would write the following style in native javascript

function fn(a, b){
    if(b! = =undefined) {console.log(a, b)
    }else{
        console.log(a)
    }
}
Copy the code

The fact that javascript does not support method overloading in typescript does not change, but it does make it easier to read and validate methods when defining and using them, such as the following typescript statement

function fn(a); // Method call of form 1, which takes 1 argument
function fn(a,b); // Method call form 2, takes 2 arguments
function fn(a,b?){ // Final function definition, a combination of both forms. The argument is followed by '? ', indicating that this parameter is not required
    if(b){
        console.log(a, b)
    }else{
        console.log(a)
    }
}
fn(1) / / right
fn(1.2) / / right
fn() // Error, the editor reported an error, does not conform to the function definition
fn(1.2.3) / / error
Copy the code

3.2 Checking variable types

One of typescript’s biggest syntactic features is that javascript variable types are qualified when declared. Changing the type of a variable will result in an error, making javascript less error-prone, for example

let isDone: boolean // Specify a variable as a Boolean type
isDone = true / / right
isDone = 'hello' // Error, cannot change the type of data
Copy the code

The definitions of common data types are summarized below

3.2.1 Common Data Types
let isDone: boolean / / a Boolean value
let num: number / / digital
let username: string / / string
let unusable: void // null (undefined or null)
let numArr: number[] // Array to store numbers
let a: any  // Any value, any type
let b: string | number // Union type, specifying one of multiple types
Copy the code
3.2.2 Function data types
  • Method 1, named function, specifiesParameter typeandReturn value data type
function sum(x: number, y: number) :number { 
    return x + y
}
Copy the code
  • Method 2, function variable, specifyParameter typeandReturn value data type
let sum: (x: number, y: number) = > number 
sum = function (x, y) {
    return x + y
}
Copy the code
3.2.3 Object Data Types
  • Mode 1-1: through the interfaceinterfaceDefine object types,Check independent variable
interface Person { // Check whether the object contains username,age, and say methods
    username: string
    age: number
    say: (message: string) = > string
}
let tom: Person 
tom = { / / right
    username: 'Tom'.age: 25.say: function(message){
        return message
    }
}
Copy the code
  • Mode 1-2, through the interfaceinterfaceDefine object types,Examine class instance objects
interface PersonInterface { // Check whether the class instance object contains username,age,say methods
    username: string
    age: number
    say: (message: string) = > string
}
class Person{ // Define the type
    constructor(username: string, age: number){
        this.username = username
        this.age = age
    }
    username: string
    age: number
    say(message){
        return message
    }
}

let tom:PersonInterface
tom = new Person('zs'.25) / / right
Copy the code
  • Method 2-1, by keywordtypeDefine object types,Check independent variable
type Person = { // Check whether the object contains username,age, and say methods
    username: string
    age: number
    say: (message: string) = > string
}
let tom: Person 
tom = { / / right
    username: 'Tom'.age: 25.say: function(message){
        return message
    }
}
Copy the code
  • Method 2-2, by keywordtypeDefine object types,Examine class instance objects
type PersonInterface = { // Check whether the class instance object contains username,age,say methods
    username: string
    age: number
    say: (message: string) = > string
}
class Person{ // Define the type
    constructor(username: string, age: number){
        this.username = username
        this.age = age
    }
    username: string
    age: number
    say(message){
        return message
    }
}

let tom:PersonInterface
tom = new Person('zs'.25) / / right
Copy the code

3.3 generics

Generics are the feature of defining functions, interfaces, or classes without specifying a specific type in advance.

  • Method 1- Used in arrays
let arr: Array<number> // Specify the type of data to store in the array
arr = [1.2.3] / / right
Copy the code
  • Mode 2- Used in methods
function createArray<T> (length: number, value: T) :Array<T> { // Specify the data type of the parameter and return value
    let result: T[] = []
    for (let i = 0; i < length; i++) {
        result[i] = value
    }
    return result
}
createArray<string> (3.'x') // Dynamically set generic 'T' to string, return data ['x', 'x', 'x']
createArray<number> (2.0) // set generic 'T' to number,[0, 0]
Copy the code
  • Mode 3- Used in class definitions
class GenericNumber<T> { // Specify the types used by variables and methods in the class
    zeroValue: T;
    add: (x: T, y: T) = > T;
}
let myGenericNumber = new GenericNumber<number> ();// Set the generic 'T' to number
myGenericNumber.zeroValue = 0; / / right
myGenericNumber.add = function(x, y) { return x + y; } / / right
Copy the code

4. Index. ts source code analysis

After looking at the typescript syntax above and looking at the code in index.ts, we conclude that the Component method has

  1. ComponentMethod implements overloading, accepting different sumsVueRelated to the type
  2. ComponentThe method does different processing internally, depending on the type of argument passed in
  3. ComponentMethod return passcomponentFactoryProcessed data
// (1) The 'Component' method is overloaded to accept different types related to 'Vue'
// The parameter is configured with the Vue attribute class. The return value is a method that accepts a subclass of Vue
function Component <V extends Vue> (options: ComponentOptions<V> & ThisType<V>).VC extends VueClass<V> > (target: VC) = >VC/ / parameters forVueThe return value isVueA subclass offunction Component <VC extends VueClass<Vue> > (target: VC) :VC/ / parameters forVueConfiguration property class orVueA subclass of, returns an arbitrary valuefunction Component (options: ComponentOptions<Vue> | VueClass<Vue>) :any {/ / (2) `ComponentThe 'method does different processing internally, depending on the type of argument passed inif (typeof options === 'function') {/ / (3) `Component'method returns through'componentFactory'Processed datareturn componentFactory(options)}return function (Component: VueClass<Vue>) {/ / (3) `Component'method returns through'componentFactory'Processed datareturn componentFactory(Component, options)}}Copy the code

5.com component.ts source preview

Next, take a look at the SRC/Component. ts source code and look at the definition of componentFactory. What does this function do

export const $internalHooks = [
  'data'.'beforeCreate'.'created'.'beforeMount'.'mounted'.'beforeDestroy'.'destroyed'.'beforeUpdate'.'updated'.'activated'.'deactivated'.'render'.'errorCaptured'./ / 2.5
  'serverPrefetch' / / 2.6
]
export function componentFactory (Component: VueClass
       
        , options: ComponentOptions
        
          = {}
        
       ) :VueClass<Vue> {
  options.name = options.name || (Component as any)._componentTag || (Component as any).name
  // prototype props.
  const proto = Component.prototype
  Object.getOwnPropertyNames(proto).forEach(function (key) {
    if (key === 'constructor') {
      return
    }

    // hooks
    if ($internalHooks.indexOf(key) > -1) {
      options[key] = proto[key]
      return
    }
    const descriptor = Object.getOwnPropertyDescriptor(proto, key)!
    if(descriptor.value ! = =void 0) {
      // methods
      if (typeof descriptor.value === 'function') {
        (options.methods || (options.methods = {}))[key] = descriptor.value
      } else {
        // typescript decorated data
        (options.mixins || (options.mixins = [])).push({
          data (this: Vue) {
            return { [key]: descriptor.value }
          }
        })
      }
    } else if (descriptor.get || descriptor.set) {
      // computed properties
      (options.computed || (options.computed = {}))[key] = {
        get: descriptor.get,
        set: descriptor.set
      }
    }
  })

  // add data hook to collect class properties as Vue instance's data; (options.mixins || (options.mixins = [])).push({ data (this: Vue) {
      return collectDataFromConstructor(this, Component)
    }
  })

  // decorate options
  const decorators = (Component as DecoratedClass).__decorators__
  if (decorators) {
    decorators.forEach(fn= > fn(options))
    delete (Component as DecoratedClass).__decorators__
  }

  // find super
  const superProto = Object.getPrototypeOf(Component.prototype)
  const Super = superProto instanceof Vue
    ? superProto.constructor as VueClass<Vue>
    : Vue
  const Extended = Super.extend(options)

  forwardStaticMembers(Extended, Component, Super)

  if (reflectionIsSupported()) {
    copyReflectionMetadata(Extended, Component)
  }

  return Extended
}
Copy the code

Analysis of the above code, we do not need to understand all the details of the code, need to grasp the main points, understand two points:

(1)componentFactoryMethod to a parameter passed inComponentWhat did

(2)componentFactoryWhat data does the method return

To understand this statement, we need to understand some es6 Object and vue syntax in component.ts

6. ES6 – Object syntax

6.1 Object. GetOwnPropertyDescriptor method

Object. GetOwnPropertyDescriptor () method returns the specified Object on a has its own corresponding attribute descriptor.

Its own attributes refer to attributes that are directly assigned to the object and do not need to be searched from the prototype chain.

Attribute descriptor refers to the feature description of object attributes, including four features

  • The specified object is true if and only if the property description of the specified object can be changed or deleted.

  • Enumerable: True if and only if the specified object’s properties can be enumerated.

  • Value: The value of the attribute (valid only for data attribute descriptors)

  • Writable: True if and only if the value of a property can be changed

As shown below

var user = {
    username: 'zs'
}
const descriptor = Object.getOwnPropertyDescriptor(user, 'username')
/* The input is a different object, which is {different: true Enumerable: true value: "ZS" writable: true} */
console.log(descriptor)
Copy the code

6.2 Object. GetOwnPropertyNames method

Object. GetOwnPropertyNames () method returns a specified by the Object of all its attributes array of property names

As shown below

var user = {
    username: 'zs'.age: 20
}
var names = Object.getOwnPropertyNames(user)
console.log(names) //['username','age']
Copy the code

6.3 Object. GetPrototypeOf method

The object.getProtoTypeof () method returns the prototype of the specified Object

As shown below

class Person {
    constructor(username, age){
        this.username = username
        this.age = age
    }
    say(){}}var p = new Person('zs'.20)
{constructor:f, say: f} */
console.log(Object.getPrototypeOf(p))
Copy the code

7. Vue – extend method

The vue.extend () method uses the base Vue constructor to create a “subclass.” The parameter is an object that contains component options

As shown below

<! DOCTYPEhtml>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <title>Document</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.11/vue.min.js"></script>
</head>
<body>
    <div id="app">
        
    </div>
</body>
<script>
var App = Vue.extend({
  template: '<p>{{firstName}} {{lastName}}</p>'.data: function () {
    return {
      firstName: 'Walter'.lastName: 'White'}}})// Create an App instance and mount it to an element.
new App().$mount('#app')
</script>
</html>
Copy the code

8.com component.ts source code analysis

After looking at the Object and Vue syntax above and looking at the component.ts code, we conclude that the componentFactory method has

  1. componentFactoryMethod in the traversal parameter, that is, the Vue componentComponent
  2. componentFactoryMethod, according to variablesComponentGenerates configuration variables for the componentoptions
  3. componentFactoryBy means ofVue.extendMethod and configuration variablesoptionsGenerates a subclass of Vue and returns that class
// List of constructor names
export const $internalHooks = [
  / /... Omit some minor code
  'created'.'mounted'./ /... Omit some minor code
]
// 
export function componentFactory (
  Component: VueClass<Vue>, // The parameter Component is an object of the Vue Component class
  options: ComponentOptions<Vue> = {} The optionsVue parameter configures the property object for the component, which defaults to an empty object
) :VueClass<Vue> { // Return a Vue object
    / /... Omit some minor code
    // Add the name attribute to the component configuration
    options.name = options.name || (Component as any)._componentTag || (Component as any).name
  const proto = Component.prototype
  1. Iterating through properties of the Component parameter
  Object.getOwnPropertyNames(proto).forEach(function (key) {
	2. Generate the configuration variable 'options' for the component
    // Add hook function properties to the component configuration
    if ($internalHooks.indexOf(key) > -1) {
      options[key] = proto[key]
      return
    }  
    // Get the attribute description
    const descriptor = Object.getOwnPropertyDescriptor(proto, key)!
    if(descriptor.value ! = =void 0) {
        // Add the methods attribute to the component configuration
        if (typeof descriptor.value === 'function') {
        	(options.methods || (options.methods = {}))[key] = descriptor.value
      	}else if (descriptor.get || descriptor.set) {
            // Add computed properties to the component configuration
            (options.computed || (options.computed = {}))[key] = {
                get: descriptor.get,
                set: descriptor.set
            }
        }
    }
    / /... Omit some minor code
    // Get the parent Vue class
    const superProto = Object.getPrototypeOf(Component.prototype)
    const Super = superProto instanceof Vue
    ? superProto.constructor as VueClass<Vue>
    : Vue
    Extend method and configuration variable 'options' to generate a subclass of Vue and return that class
    // Call the extend method of the parent class, that is, generate a subclass of Vue by vue.extend (options)
    const Extended = Super.extend(options)  
    // Returns the Vue object generated by the processing
    return Extended
  })
}
Copy the code

9. Write a simple one yourselfvue-class-component

9.1 Step 1: Create the project, install dependencies, and write configuration files

  • Create a folder called write-vue-class-Component

  • Execute NPM init -y to generate package.json

  • Install Babel dependencies

    npm install –save-dev @babel/cli @babel/core @babel/preset-env @babel/node

    npm install –save @babel/polyfill

    npm install –save-dev @babel/plugin-proposal-decorators

    npm install –save-dev @babel/plugin-proposal-class-properties

  • Install vUE dependencies

    npm install vue

  • Create the Babel. Config. Js

const presets = [
    ["@babel/env", {targets: {edge:"17".firefox:"60".chrome:"67".safari:"11.1"}}]]const plugins = [
    ["@babel/plugin-proposal-decorators", { "legacy": true }],
    ["@babel/plugin-proposal-class-properties", { "loose": true}]]module.exports = { presets, plugins }
Copy the code

9.2 Creating decorator Component.js

import Vue from 'vue'
// List of constructor names
const $internalHooks = [ 
    'created'.'mounted'
]
function componentFactory (Component, options = {}) { 
    const proto = Component.prototype
    // Iterate over the properties of the Component parameter
    Object.getOwnPropertyNames(proto).forEach(function (key) {
      // Add hook function properties to the component configuration
      if ($internalHooks.indexOf(key) > -1) {
        options[key] = proto[key]
        return
      }  
      // Get the attribute description
      const descriptor = Object.getOwnPropertyDescriptor(proto, key)
      if(descriptor.value ! = =void 0) {
          // Add the methods attribute to the component configuration
          if (typeof descriptor.value === 'function') {
              (options.methods || (options.methods = {}))[key] = descriptor.value
          }else if (descriptor.get || descriptor.set) {
              // Add computed properties to the component configuration
              (options.computed || (options.computed = {}))[key] = {
                  get: descriptor.get,
                  set: descriptor.set
           	  }
          }
        }      
        // Subclasses of Vue are generated by vue.extend (options)
        const Extended = Vue.extend(options)
        // Returns the Vue object generated by the processing
        return Extended
    })
}

function Component (options) {
    if (typeof options === 'function') {
      return componentFactory(options)
    }
    return function (Component) {
      return componentFactory(Component, options)
    }
}
export default Component
Copy the code

9.3 Create test code index.js

import Vue from 'vue'
import Component from './component.js'    

@Component({
    filters: { // Define the filter
        upperCase: function (value) {
            return value.toUpperCase()
        }
    }
})
class User extends Vue {
    firstName = ' '// Define the data variable
    lastName = ' '  
    created(){ // Define the lifecycle function
        this.firstName = 'li'
        this.lastName = 'lei'    
    }
    handleClick(){ // define methods
        this.firstName = ' '
        this.lastName = ' ' 
    }
    get fullName() { // Define computed properties
        return this.firstName + ' ' + this.lastName
    }
}

let u = new User()
console.log(u)
Copy the code

9.4 Running the Test Code

npx babel-node index.js
Copy the code

Run successfully, view the vUE generated object

10. Notice

In the next section, we’ll look at how to use vue-class-Component in a project and what to do with it