vue jsx与render的区别及基本使用

  vue template语法简单明了,数据操作与视图分离,开发体验友好。但是在某些特定场合中,会限制一些功能的扩展,如动态使用过滤器解析字符串类型的模板文件等。以上功能的实现可以借助vue的render语法,render语法比template更偏底层,允许在HTML中使用js语法,可以极大的扩展HTML的能力。

  render函数注入了一个参数createElement,用来创建我们所需要的标签内容,有三个参数:HTML标签(elementTag)标签属性(option)子元素(children);从createElement的参数列表里面可以看出,如果组件内部结构嵌套比较深,render的语法写起来会比较繁琐,需要不断的调用createElement,对于想偷懒的我,还是想想有没有什么比较简易的写法,jsx无疑是一种很好的选择,区别在于jsx可以像我们写HTML文件一样写业务代码,借助于babel,会将jsx语法转换成render语法,没有什么副作用。

  最近在使用ant-design-vue进行项目重构,现就以ant-desigin-vue为例,介绍下vue jsx的基本使用。

  1、安装vue jsx开发环境

    已vue-cli 3.0脚手架为例,首先需要安装babel转义插件,将vue jsx转义成vue的render语法,执行以下命令:

npm install @vue/babel-preset-jsx @vue/babel-helper-vue-jsx-merge-props -D

    其次,修改babel配置文件(babel.config.js/.babelrc),如下:

module.exports = {
    presets: [
       ['@vue/app', {
           useBuiltIns: 'entry'
       }],
        ['@vue/babel-preset-jsx',
        {
            "injectH": false
        }]
    ]
};

  注意@vue/babel-preset-jsx默认会注入一个h(createElement的语法糖),会与vue render函数注入的createElement冲突,这个配置要设置false,否则项目启动会报错

  2、基本使用

    jsx并不能解析vue指令,因此template转jsx语法时,每一部分的对应写法和vue官网文档一致,如下:

{
  // 与 `v-bind:class` 的 API 相同,
  // 接受一个字符串、对象或字符串和对象组成的数组
  'class': {
    foo: true,
    bar: false
  },
  // 与 `v-bind:style` 的 API 相同,
  // 接受一个字符串、对象,或对象组成的数组
  style: {
    color: 'red',
    fontSize: '14px'
  },
  // 普通的 HTML 特性
  attrs: {
    id: 'foo'
  },
  // 组件 prop
  props: {
    myProp: 'bar'
  },
  // DOM 属性
  domProps: {
    innerHTML: 'baz'
  },
  // 事件监听器在 `on` 属性内,
  // 但不再支持如 `v-on:keyup.enter` 这样的修饰器。
  // 需要在处理函数中手动检查 keyCode。
  on: {
    click: this.clickHandler
  },
  // 仅用于组件,用于监听原生事件,而不是组件内部使用
  // `vm.$emit` 触发的事件。
  nativeOn: {
    click: this.nativeClickHandler
  },
  // 自定义指令。注意,你无法对 `binding` 中的 `oldValue`
  // 赋值,因为 Vue 已经自动为你进行了同步。
  directives: [
    {
      name: 'my-custom-directive',
      value: '2',
      expression: '1 + 1',
      arg: 'foo',
      modifiers: {
        bar: true
      }
    }
  ],
  // 作用域插槽的格式为
  // { name: props => VNode | Array<VNode> }
  scopedSlots: {
    default: props => createElement('span', props.text)
  },
  // 如果组件是其它组件的子组件,需为插槽指定名称
  slot: 'name-of-slot',
  // 其它特殊顶层属性
  key: 'myKey',
  ref: 'myRef',
  // 如果你在渲染函数中给多个元素都应用了相同的 ref 名,
  // 那么 `$refs.myRef` 会变成一个数组。
  refInFor: true
}

  具名插槽:{ this.$slots.slotName }

  v-for转换:

const formItems = this.formFields.map(vv => {
            const itemProps = {
                props: {
                    label: vv.label
                }
            };
            if (Object.is(vv.type, 'input')) {
                const inputProps = {
                    domProps: {
                        autocomplete: 'off'
                    },
                    directives: [{
                        name: 'decorator',
                        rawName: 'v-decorator',
                        value: [vv.prop, {rules: vv.rules}]
                    }]
                };
                return <a-form-item {...itemProps}>
                    <a-input {...inputProps} />
                </a-form-item>
            }
        });

  附一个基于ant-design-vue form封装的弹窗表格组件:

/**
 * 简单的通用表单弹框
 * 配置文件以vue jsx为准
 */
//中间量,用于CustomizedForm与keCommonForm之间数据的传输
const CustomizedForm = {
    props: {
        formFields: {
            type: Array,
            default() {
                return [];
            }
        },
        layout: {
            type: String,
            default: 'inline'
        }
    },
    created () {
        let _this = this;
        this.form = this.$form.createForm(this, {
            onFieldsChange: (props, changedFields) => {
                _this.$emit('fieldsChange', changedFields);
            },
            mapPropsToFields: () => {
                let formFieldsBean = {};
                _this.formFields.forEach(vv => {
                    formFieldsBean[vv.prop] = _this.$form.createFormField({
                        value: vv.value
                    });
                });
                return formFieldsBean;
            },
            onValuesChange (props, value, currentValue) {
                _this.$emit('change', currentValue);
            }
        });
        //传递form对象
        this.$emit('childScope', this.form);
    },
    render() {
        let _this = this;
        
        const formProps = {
            props: {
                layout: this.layout,
                form: this.form
            },
            attrs: {
                class: 'ke-form ke-form-inline'
            }
        };
        
        const formItems = this.formFields.map(vv => {
            const itemProps = {
                props: {
                    label: vv.label
                }
            };
            //深拷贝一份表单属性,防止报错
            const inputProps = {
                attrs: {
                    autocomplete: 'off'
                },
                scopedSlots: {},
                directives: [{
                    name: 'decorator',
                    rawName: 'v-decorator',
                    value: [vv.prop, {rules: vv.rules}]
                }]
            };
            //如果有配置表单信息,合并表单属性
            if (_.isObject(vv.options)) {
                Object.assign(inputProps, vv.options);
            }
            //如果存在插槽,则配置插槽信息
            if (_.isObject(vv.scopedSlots)) {
                Object.keys(vv.scopedSlots).forEach(key => {
                    let formItemOptions = _.cloneDeep(vv.scopedSlots[key].options);
                    inputProps.scopedSlots[key] =
                            () => _this.$createElement(vv.scopedSlots[key].tagName, formItemOptions);
                })
            }
            //获取创建元素的tagName
            let tagName = vv.tagName || 'a-input';
            let item = this.$createElement(tagName, inputProps);
            return <a-form-item {...itemProps}>
                { item }
            </a-form-item>
        });
        
        return (
            <a-form {...formProps}>
                { formItems }
            </a-form>
        );
    }
};

/**
 * 简单的表单可以引用此组件
 */
export default {
    name: 'keCommonForm',
    model: {
        prop: 'value',
        event: 'change'
    },
    props: {
        title: {
            type: String,
            default: '提示'
        },
        visible: {
            type: Boolean,
            default: false
        },
        okText: {
            type: String,
            default: ''
        },
        layout: {//表格的排列方式 'horizontal'|'vertical'|'inline'
            type: String,
            default: 'inline'
        },
        formFields: {//表格的配置文件Array<Object>
            type: Array,
            default() {
                return []
            }
        },
        value: {
            type: Object,
            default() {
                return {}
            }
        },
        submitFun: {//提交表单触发父组件内部的方法
            type: String,
            default: 'test'
        },
        closeResetFields: {//关闭模态框重置表单内容
            type: Boolean,
            default: true
        },
        commonFormOption: {
            type: Object,
            default() {
                return {}
            }
        }
    },
    components: { CustomizedForm },
    methods: {
        onCancel() {
            this.$emit('closeCommonForm')
        },
        onOk() {
            let checkedList = this.formFields.map(vv => vv.prop);
            if (this._form && _.isFunction(this._form.validateFieldsAndScroll)) {
               this._form.validateFieldsAndScroll(checkedList, (errors, value) => {
                   if (errors && _.isObject(errors) && Object.keys(errors).length > 0) {
                       // Object.keys(errors).forEach(vv => {
                       //     this.$message.error(errors[vv].errors[0].message, 2);
                       // })
                       return;
                   }else {
                       this.$emit('submit', this.submitFun);
                   }
               })
            }
            
        },
        formChange(value) {
            this.$emit('change', value);
        }
    },
    render() {
        //模态框关闭后的回调
        let _this = this;
        function afterClose() {
            if (_this.closeResetFields) {
                _this._form.resetFields();
            }
        }
        
        const confirmBtnProps = {
            props: {
                type: 'primary',
                'html-type': 'submit'
            },
            on: {
                click: this.onOk
            }
        };
        
        const modalProps = {
            props: {
                title: this.title,
                okText: this.okText,
                visible: this.visible,
                afterClose: afterClose
            },
            on: {
                cancel: this.onCancel,
                on: this.onOk
            },
            scopedSlots: {
                footer: scope => {
                    return <div>
                        <a-button { ...confirmBtnProps }>提交</a-button>
                        <a-button onClick={ this.onCancel }>取消</a-button>
                    </div>
                }
            }
        };
        //合并通用表单配置文件,支持jsx语法
        if (_.isObject(this.commonFormOption)) {
            Object.assign(modalProps, this.commonFormOption);
        }
    
        
        const customizedFormProps = {
            props: {
                formFields: this.formFields,
                layout: this.layout
            },
            on: {
                change: this.formChange,
                childScope: form => {
                    this._form = form;
                }
            }
        };
        return (
            <a-modal {...modalProps}>
                { this.$slots.content }
                <CustomizedForm { ...customizedFormProps } />
            </a-modal>
        )
    }
}

 

posted @ 2019-06-14 14:50  Gerryli  阅读(7222)  评论(0编辑  收藏  举报