美文网首页
前端框架系列之(vue-property-decorator)

前端框架系列之(vue-property-decorator)

作者: vv_小虫虫 | 来源:发表于2020-06-22 21:34 被阅读0次

    简介:

    这节我们继续解析一个叫vue-property-decorator的第三方库,首先可以看它官网的一个介绍:

    This library fully depends on [vue-class-component](https://github.com/vuejs/vue-class-component), so please read its README before using this library.

    也就是说它是基于vue-class-component库的,在上一篇文章中我们介绍了如何在vue中利用装饰器使用类组件,我们写了一篇叫vue-class-component的文章,大家有兴趣可以去看一下。

    实现:

    创建工程:

    我们直接copy一份上一节代码的demo,然后让它支持一下typescript

    vue-property-decorator-demo

    vue-property-decorator-demo
        demo
        index.html //页面入口文件
        lib
        main.js //webpack打包过后的文件
        src
        view
            component.d.ts //类组件ts声明文件
                component.js //类组件文件
            demo-class.vue //demo组件
            main.ts //应用入口文件
        babel.config.js //babel配置文件
        tsconfig.json //ts配置文件
        package.json //项目清单文件
        webpack.config.js //webpack配置文件
    

    index.html:

    我们直接引用打包过后的文件

    <!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:

    加载demo.vue组件,挂在到“#app”元素上

    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) {
        //创建一个组件实例
        const data = new Component();
        const plainData = {};
        //遍历当前对象的属性值
        Object.keys(data).forEach(key => {
            if (data[key] !== void 0) {
                plainData[key] = data[key];
            }
        });
        //返回属性值
        return plainData
    }
    /**
     * 组件工程函数
     * @param Component //当前类组件
     * @param options //参数
     */
    function componentFactory(Component, options = {}) {
        options.name = options.name || Component.name; //如果options没有name属性的话就直接使用类名
        //获取类的原型
        const proto = Component.prototype;
        //遍历原型上面的属性
        Object.getOwnPropertyNames(proto).forEach((key) => {
            // 过滤构造方法
            if (key === 'constructor') {
                return
            }
            // 赋值vue自带的一些方法
            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 {
                    //不是方法属性的话就通过mixins方式直接赋值给data
                    (options.mixins || (options.mixins = [])).push({
                        data() {
                            return {[key]: descriptor.value}
                        }
                    });
                }
            }
        });
        //通过类实例获取类属性值通过mixins给data
        (options.mixins || (options.mixins = [])).push({
            data(){
                return collectDataFromConstructor(this, Component)
            }
        });
    
        //获取当前类的父类
        const superProto = Object.getPrototypeOf(Component.prototype);
        //获取Vue
        const Super = superProto instanceof Vue
            ? superProto.constructor
            : Vue;
        //使用Vue.extend方法创建一个vue组件
        const Extended = Super.extend(options);
        //直接返回一个Vue组件
        return Extended
    }
    
    /**
     * 组件装饰器
     * @param options 参数
     * @returns {Function} 返回一个vue组件
     */
    export default function Component(options) {
        //判断有没有参数
        if (typeof options === 'function') {
            return componentFactory(options)
        }
        return function (Component) {
            return componentFactory(Component, options)
        }
    }
    

    babel.config.js:

    babel的配置跟上一节的是一样的,大家感兴趣可以去看一下前端框架系列之(装饰器Decorator

    module.exports = {
        "presets": [
            ["@babel/env", {"modules": false}]
        ],
        "plugins": [
            ["@babel/plugin-proposal-decorators", {"legacy": true}],
            ["@babel/proposal-class-properties", {"loose": true}]
        ]
    };
    

    package.json:

    因为要编译vue文件所以我们加入了webpack跟vue、vue-loader等依赖

    {
      "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
    }
    
    

    运行工程:

    npm  run dev
    

    浏览器打开,http://127.0.0.1:8081/demo/index.html

    我们可以看到:

    在这里插入图片描述

    好啦,我们的工程就创建完毕了。

    实现效果:

    main.ts:

    import Vue from "vue";
    import Demo from "./view/demo-class.vue";
    new Vue({
        render(h){
            return h(Demo,{
                props:{
                    msg: "我是自定义属性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>
    
    

    好了,我们照着最终的样子实现一下代码。

    代码实现:

    我们首先修改一下我们的component.js文件:

    /**
     * 组件工程函数
     * @param Component //当前类组件
     * @param options //参数
     */
    function componentFactory(Component, options = {}) {
        options.name = options.name || Component.name; //如果options没有name属性的话就直接使用类名
        //获取类的原型
        const proto = Component.prototype;
        //遍历原型上面的属性
        Object.getOwnPropertyNames(proto).forEach((key) => {
            // 过滤构造方法
            if (key === 'constructor') {
                return
            }
            // 赋值vue自带的一些方法
            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 {
                    //不是方法属性的话就通过mixins方式直接赋值给data
                    (options.mixins || (options.mixins = [])).push({
                        data() {
                            return {[key]: descriptor.value}
                        }
                    });
                }
            }
        });
        //通过类实例获取类属性值通过mixins给data
        (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__
        }
    
        //获取当前类的父类
        const superProto = Object.getPrototypeOf(Component.prototype);
        //获取Vue
        const Super = superProto instanceof Vue
            ? superProto.constructor
            : Vue;
        //使用Vue.extend方法创建一个vue组件
        const Extended = Super.extend(options);
        //直接返回一个Vue组件
        return Extended
    }
    

    可以看到,我们加一了一段代码:

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

    就是在Component类上绑定了一个decorators属性供给其它地方使用,其实地方是哪呢?对的,就是我们的view-property-decorator.ts,做法很简单,就是把我们类组件的options对象暴露出去通过decorators属性提供给其它地方。

    那么类组件中的decorators属性怎么给其它地方用呢?

    我们继续在我们的component.js中提供一个createDecorator方法

    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) {
        //创建一个组件实例
        const data = new Component();
        const plainData = {};
        //遍历当前对象的属性值
        Object.keys(data).forEach(key => {
            if (data[key] !== void 0) {
                plainData[key] = data[key];
            }
        });
        //返回属性值
        return plainData
    }
    
    /**
     * 组件工程函数
     * @param Component //当前类组件
     * @param options //参数
     */
    function componentFactory(Component, options = {}) {
        options.name = options.name || Component.name; //如果options没有name属性的话就直接使用类名
        //获取类的原型
        const proto = Component.prototype;
        //遍历原型上面的属性
        Object.getOwnPropertyNames(proto).forEach((key) => {
            // 过滤构造方法
            if (key === 'constructor') {
                return
            }
            // 赋值vue自带的一些方法
            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 {
                    //不是方法属性的话就通过mixins方式直接赋值给data
                    (options.mixins || (options.mixins = [])).push({
                        data() {
                            return {[key]: descriptor.value}
                        }
                    });
                }
            }
        });
        //通过类实例获取类属性值通过mixins给data
        (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__
        }
    
        //获取当前类的父类
        const superProto = Object.getPrototypeOf(Component.prototype);
        //获取Vue
        const Super = superProto instanceof Vue
            ? superProto.constructor
            : Vue;
        //使用Vue.extend方法创建一个vue组件
        const Extended = Super.extend(options);
        //直接返回一个Vue组件
        return Extended
    }
    
    /**
     * 组件装饰器
     * @param options 参数
     * @returns {Function} 返回一个vue组件
     */
    export default function Component(options) {
        //判断有没有参数
        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))
        }
    }
    

    接下来我们创建一个view-property-decorator.ts文件:

    import {createDecorator} from "./component";
    
    /**
     *  属性装饰器
     * @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) {
        //获取类组件的options属性,把当前属性的options赋给类组件options的props属性
        createDecorator(function (componentOptions: any, k: string) {
          (componentOptions.props || (componentOptions.props = {}))[k] = options;
        })(target, key);
      };
    }
    

    代码很简单,对装饰器修饰属性不熟悉的童鞋,可以看一下我前面写的一篇文章前端框架系列之(装饰器Decorator)

    最终效果:

    在这里插入图片描述

    在vue-property-decorator中还有一些其它功能:

    我们只是简单的实现了一下@Prop,感兴趣的小伙伴可以自己去clone一份源码

    把其它功能都实现一下,你会发现有不一样的收获的~~

    总结:

    写了三篇关于装饰器的文章了,我觉得就对类组件这一块的话,特别是接触过一些其它语言,比如java的童鞋来说,用起来是真的爽!!,但是就像java中的注解一样,我们也是一步一步的用装饰器实现了一下类组件,性能方面的话肯定是比不上函数组件的,因为我们每一个类组件就相对于在内存中创建了一个实例对象,这是很占内存的,这是它的弊端,但是好处我就不多说了吧,更贴近面向对象语言设计,特别是结合typescript,然后在一些多人合作的项目上还是发挥着很大的作用的,就增加一点点内存的话其实也还能接受。

    好啦! 这节就到这里了,下面一节我将会利用类组件结合项目需求做mvc、mvp、mvvp架构模式的演示,大家敬请期待!!

    相关文章

      网友评论

          本文标题:前端框架系列之(vue-property-decorator)

          本文链接:https://www.haomeiwen.com/subject/bqoffktx.html