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) Component
Method definition
(2) componentFactory
Methods 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 interface
interface
Define 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 interface
interface
Define 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 keyword
type
Define 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 keyword
type
Define 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
Component
Method implements overloading, accepting different sumsVue
Related to the typeComponent
The method does different processing internally, depending on the type of argument passed inComponent
Method return passcomponentFactory
Processed 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)componentFactory
Method to a parameter passed inComponent
What did
(2)componentFactory
What 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
componentFactory
Method in the traversal parameter, that is, the Vue componentComponent
componentFactory
Method, according to variablesComponent
Generates configuration variables for the componentoptions
componentFactory
By means ofVue.extend
Method and configuration variablesoptions
Generates 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