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