vue jsx與render的區別及基本使用

  vue template語法簡單明瞭,數據操做與視圖分離,開發體驗友好。可是在某些特定場合中,會限制一些功能的擴展,如動態使用過濾器解析字符串類型的模板文件等。以上功能的實現能夠藉助vue的render語法,render語法比template更偏底層,容許在HTML中使用js語法,能夠極大的擴展HTML的能力。html

  render函數注入了一個參數createElement,用來建立咱們所須要的標籤內容,有三個參數:HTML標籤(elementTag)標籤屬性(option)子元素(children);從createElement的參數列表裏面能夠看出,若是組件內部結構嵌套比較深,render的語法寫起來會比較繁瑣,須要不斷的調用createElement,對於想偷懶的我,仍是想一想有沒有什麼比較簡易的寫法,jsx無疑是一種很好的選擇,區別在於jsx能夠像咱們寫HTML文件同樣寫業務代碼,藉助於babel,會將jsx語法轉換成render語法,沒有什麼反作用。vue

  最近在使用ant-design-vue進行項目重構,現就以ant-desigin-vue爲例,介紹下vue jsx的基本使用。vue-cli

  一、安裝vue jsx開發環境express

    已vue-cli 3.0腳手架爲例,首先須要安裝babel轉義插件,將vue jsx轉義成vue的render語法,執行如下命令:npm

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,不然項目啓動會報錯babel

  二、基本使用app

    jsx並不能解析vue指令,所以template轉jsx語法時,每一部分的對應寫法和vue官網文檔一致,以下:dom

{
  // 與 `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>
        )
    }
}
相關文章
相關標籤/搜索