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> ) } }