Introduction:
In this section, we will continue to analyze a vue-property-decorator First of all, you can see an introduction of its official website:
This library fully depends on [vue-class-component](https://github.com/vuejs/vue-class-component), so please read its README before using this library.
That is to say, it is based on the vue class component library. In the previous article, we introduced how to use class components with decorators in vue. We wrote an article called vue-class-component If you are interested, you can go and have a look.
realization:
Create project:
Let's copy the demo of the previous code directly, and then let it support typescript
vue-property-decorator-demo
vue-property-decorator-demo demo index.html //Page entry file lib main.js //Web pack packed files src view component.d.ts //Class component ts declaration file component.js //Class component file demo-class.vue //demo component main.ts //Application Portal file babel.config.js //babel profile tsconfig.json //ts profile package.json //Project list document webpack.config.js //webpack profile
index.html:
We directly refer to the packed file
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="app"></div> <script src="http://127.0.0.1:8081/main.js"></script> </body> </html>
demo-class.vue:
<template> <div @click="say()">{{msg}}</div> </template> <script lang="ts"> import Vue from "vue"; import Component from "./component"; @Component class DemoComponent extends Vue{ msg = 'hello world'; say(){ alert(this.msg); } } export default DemoComponent; </script>
main.ts:
load demo.vue Component, hanging on the "app" element
import Vue from "vue"; import Demo from "./view/demo-class.vue"; new Vue({ render(h){ return h(Demo); } }).$mount("#app");
component.d.ts:
export declare const $internalHooks: string[]; export default function componentFactory(Component: any, options?: any): any;
component.js:
import Vue from "vue"; export const $internalHooks = [ 'data', 'beforeCreate', 'created', 'beforeMount', 'mounted', 'beforeDestroy', 'destroyed', 'beforeUpdate', 'updated', 'activated', 'deactivated', 'render', 'errorCaptured', // 2.5 'serverPrefetch' // 2.6 ]; function collectDataFromConstructor(vm,Component) { //Create a component instance const data = new Component(); const plainData = {}; //Traverses the property values of the current object Object.keys(data).forEach(key => { if (data[key] !== void 0) { plainData[key] = data[key]; } }); //Return property value return plainData } /** * Component engineering function * @param Component //Current class component * @param options //parameter */ function componentFactory(Component, options = {}) { options.name = options.name || Component.name; //Use the class name directly if options does not have the name attribute //Get the prototype of the class const proto = Component.prototype; //Traverse the properties on the prototype Object.getOwnPropertyNames(proto).forEach((key) => { // Filter construction method if (key === 'constructor') { return } // Some methods of valuing vue if ($internalHooks.indexOf(key) > -1) { options[key] = proto[key]; return } //Get property descriptor const descriptor = Object.getOwnPropertyDescriptor(proto, key); if (descriptor.value !== void 0) { //If it's a method, assign it directly to the methods property if (typeof descriptor.value === 'function') { (options.methods || (options.methods = {}))[key] = descriptor.value; } else { //If it is not a method property, it is assigned to data directly through mixins (options.mixins || (options.mixins = [])).push({ data() { return {[key]: descriptor.value} } }); } } }); //Get the class attribute value through the class instance and give data through mixins (options.mixins || (options.mixins = [])).push({ data(){ return collectDataFromConstructor(this, Component) } }); //Get the parent of the current class const superProto = Object.getPrototypeOf(Component.prototype); //Get Vue const Super = superProto instanceof Vue ? superProto.constructor : Vue; //use Vue.extend Method to create a vue component const Extended = Super.extend(options); //Directly return to a Vue component return Extended } /** * Component decorator * @param options parameter * @returns {Function} Return a vue component */ export default function Component(options) { //Judge whether there are parameters if (typeof options === 'function') { return componentFactory(options) } return function (Component) { return componentFactory(Component, options) } }
babel.config.js:
The configuration of babel is the same as that in the last section. If you are interested, please take a look Front frame series (Decorator
module.exports = { "presets": [ ["@babel/env", {"modules": false}] ], "plugins": [ ["@babel/plugin-proposal-decorators", {"legacy": true}], ["@babel/proposal-class-properties", {"loose": true}] ] };
package.json:
Because we need to compile vue files, we add webpack, vue, vue loader and other dependencies
{ "name": "decorator-demo", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "dev": "webpack-dev-server" }, "author": "", "license": "ISC", "devDependencies": { "@babel/cli": "^7.10.1", "@babel/core": "^7.10.2", "@babel/plugin-proposal-class-properties": "^7.10.1", "@babel/plugin-proposal-decorators": "^7.10.1", "@babel/preset-env": "^7.10.2", "babel-loader": "^8.1.0", "ts-loader": "^7.0.5", "vue-loader": "^15.9.2", "vue-template-compiler": "^2.6.11", "webpack": "^4.43.0", "webpack-cli": "^3.3.11", "webpack-dev-server": "^3.11.0" }, "dependencies": { "typescript": "^3.9.5", "vue": "^2.6.11" } }
webpack.config.js:
const VueLoaderPlugin = require('vue-loader/lib/plugin'); const path = require('path'); module.exports = { mode: 'development', context: __dirname, entry: './src/main.ts', output: { path: path.join(__dirname, 'lib'), filename: 'main.js' }, resolve: { alias: { vue$: 'vue/dist/vue.esm.js' }, extensions: ['.ts', '.tsx', '.js'] }, module: { rules: [ { test: /\.tsx?$/, exclude: /node_modules/, use: [ 'babel-loader', { loader: 'ts-loader', options: { appendTsSuffixTo: [/\.vue$/], appendTsxSuffixTo: [/\.vue$/] } } ] }, { test: /\.js$/, exclude: /node_modules/, use: [ 'babel-loader', ] }, { test: /\.vue$/, use: ['vue-loader'] } ] }, devtool: 'source-map', plugins: [ new VueLoaderPlugin(), new (require('webpack/lib/HotModuleReplacementPlugin'))() ] };
tsconfig.json:
{ "compilerOptions": { "target": "esnext", "lib": [ "dom", "esnext" ], "module": "es2015", "moduleResolution": "node", "experimentalDecorators": true, "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "jsx": "preserve", "jsxFactory": "h" }, "include": [ "./**/*.ts" ], "compileOnSave": false }
Operation engineering:
npm run dev
Browser open, http://127.0.0.1:8081/demo/index.html
We can see:
Well, our project is finished.
Effect:
main.ts:
import Vue from "vue"; import Demo from "./view/demo-class.vue"; new Vue({ render(h){ return h(Demo,{ props:{ msg: "I am a custom property msg" } }); } }).$mount("#app");
demo-class.vue:
<template> <div @click="say()">{{msg}}</div> </template> <script lang="ts"> import Vue from "vue"; import Component from "./component"; import {Prop} from "./view-property-decorator"; @Component class DemoComponent extends Vue{ @Prop({type: String,default: 'hello world'})msg!: string; say(){ alert(this.msg); } } export default DemoComponent; </script>
OK, let's implement the code in the final form.
Code implementation:
Let's first revise our component.js File:
/** * Component engineering function * @param Component //Current class component * @param options //parameter */ function componentFactory(Component, options = {}) { options.name = options.name || Component.name; //Use the class name directly if options does not have the name attribute //Get the prototype of the class const proto = Component.prototype; //Traverse the properties on the prototype Object.getOwnPropertyNames(proto).forEach((key) => { // Filter construction method if (key === 'constructor') { return } // Some methods of valuing vue if ($internalHooks.indexOf(key) > -1) { options[key] = proto[key]; return } //Get property descriptor const descriptor = Object.getOwnPropertyDescriptor(proto, key); if (descriptor.value !== void 0) { //If it's a method, assign it directly to the methods property if (typeof descriptor.value === 'function') { (options.methods || (options.methods = {}))[key] = descriptor.value; } else { //If it is not a method property, it is assigned to data directly through mixins (options.mixins || (options.mixins = [])).push({ data() { return {[key]: descriptor.value} } }); } } }); //Get the class attribute value through the class instance and give data through mixins (options.mixins || (options.mixins = [])).push({ data() { return collectDataFromConstructor(this, Component) } }); // decorate options const decorators = Component.__decorators__; if (decorators) { decorators.forEach(fn => fn(options)); delete Component.__decorators__ } //Get the parent of the current class const superProto = Object.getPrototypeOf(Component.prototype); //Get Vue const Super = superProto instanceof Vue ? superProto.constructor : Vue; //use Vue.extend Method to create a vue component const Extended = Super.extend(options); //Directly return to a Vue component return Extended }
As you can see, we add a piece of code:
// decorate options const decorators = Component.__decorators__; if (decorators) { decorators.forEach(fn => fn(options)); delete Component.__decorators__ }
Is to bind a__ decorators__ Attribute is used in other places. In fact, what is the place? Yes, our view property- decorator.ts It is very simple to expose the options objects of our class components through the__ decorators__ Properties are provided elsewhere.
Then the__ decorators__ How can attributes be used elsewhere?
We continue in our component.js A createDecorator method is provided in
export function createDecorator(factory, key, index) { return (target, key, index) => { const Ctor = typeof target === 'function' ? target : target.constructor; if (!Ctor.__decorators__) { Ctor.__decorators__ = [] } if (typeof index !== 'number') { index = undefined } Ctor.__decorators__.push(options => factory(options, key, index)) } }
component.js:
import Vue from "vue"; export const $internalHooks = [ 'data', 'beforeCreate', 'created', 'beforeMount', 'mounted', 'beforeDestroy', 'destroyed', 'beforeUpdate', 'updated', 'activated', 'deactivated', 'render', 'errorCaptured', // 2.5 'serverPrefetch' // 2.6 ]; function collectDataFromConstructor(vm, Component) { //Create a component instance const data = new Component(); const plainData = {}; //Traverses the property values of the current object Object.keys(data).forEach(key => { if (data[key] !== void 0) { plainData[key] = data[key]; } }); //Return property value return plainData } /** * Component engineering function * @param Component //Current class component * @param options //parameter */ function componentFactory(Component, options = {}) { options.name = options.name || Component.name; //Use the class name directly if options does not have the name attribute //Get the prototype of the class const proto = Component.prototype; //Traverse the properties on the prototype Object.getOwnPropertyNames(proto).forEach((key) => { // Filter construction method if (key === 'constructor') { return } // Some methods of valuing vue if ($internalHooks.indexOf(key) > -1) { options[key] = proto[key]; return } //Get property descriptor const descriptor = Object.getOwnPropertyDescriptor(proto, key); if (descriptor.value !== void 0) { //If it's a method, assign it directly to the methods property if (typeof descriptor.value === 'function') { (options.methods || (options.methods = {}))[key] = descriptor.value; } else { //If it is not a method property, it is assigned to data directly through mixins (options.mixins || (options.mixins = [])).push({ data() { return {[key]: descriptor.value} } }); } } }); //Get the class attribute value through the class instance and give data through mixins (options.mixins || (options.mixins = [])).push({ data() { return collectDataFromConstructor(this, Component) } }); // decorate options const decorators = Component.__decorators__; if (decorators) { decorators.forEach(fn => fn(options)); delete Component.__decorators__ } //Get the parent of the current class const superProto = Object.getPrototypeOf(Component.prototype); //Get Vue const Super = superProto instanceof Vue ? superProto.constructor : Vue; //use Vue.extend Method to create a vue component const Extended = Super.extend(options); //Directly return to a Vue component return Extended } /** * Component decorator * @param options parameter * @returns {Function} Return a vue component */ export default function Component(options) { //Judge whether there are parameters if (typeof options === 'function') { return componentFactory(options) } return function (Component) { return componentFactory(Component, options) } } export function createDecorator(factory, key, index) { return (target, key, index) => { const Ctor = typeof target === 'function' ? target : target.constructor; if (!Ctor.__decorators__) { Ctor.__decorators__ = [] } if (typeof index !== 'number') { index = undefined } Ctor.__decorators__.push(options => factory(options, key, index)) } }
Next, we create a view property- decorator.ts File:
import {createDecorator} from "./component"; /** * Attribute decorator * @param options * @returns {(target: any, key: string) => any} * @constructor */ export function Prop(options: any) { if (options === void 0) { options = {}; } return function (target: any, key: string) { //Get the options property of the class component, and assign the options of the current property to the props property of the class component options createDecorator(function (componentOptions: any, k: string) { (componentOptions.props || (componentOptions.props = {}))[k] = options; })(target, key); }; }
The code is very simple. For children's shoes that are not familiar with decorator decoration properties, please take a look at an article I wrote earlier Front frame series (Decorator)
Final effect:
There are other functions in Vue property decorator:
- @Prop
- @PropSync
- @Model
- @Watch
- @Provide
- @Inject
- @ProvideReactive
- @InjectReactive
- @Emit
- @Ref
- @Component (provided by vue-class-component)
- Mixins (the helper function named mixins provided by vue-class-component)
We just implemented @ Prop. Interested partners can go to clone for a source code
If you implement other functions, you will find that there are different gains
Summary:
I've written three articles about decorators. I think it's really cool for class components, especially for other languages, such as java children's shoes!! , but just like the annotation in java, we use decorators to implement class components step by step. In terms of performance, we can't compare with function components, because each class component takes up a lot of memory compared with creating an instance object in memory. This is its disadvantage, but I won't say much about the benefits. It's closer to object-oriented language Speech design, especially the combination of typescript, plays a great role in some multi-user cooperation projects. It is acceptable to add a little memory.
All right! This is the end of this section. In the next section, I will use class components to demonstrate mvc, mvp, and mvvp architecture patterns in combination with project requirements. Please look forward!!