Vue 做为日前最为火热的前端框架之一,其流行程度很大部分得益于对开发者友好。尤为是SFC(单文件组件)的模式深得人心,开发者经过在一个文件里同时书写模板,JS 逻辑以及样式,就能完成组件的封装,相比其余方式,组件更加内聚,便于维护。html
在 Vue 2.0 版本以后,Vue 增长了 vdom
以及 render
函数等新特性, template 模板并不会直接生成真实的 dom, 而是先编译为 render
函数, 再由 render
函数 生成虚拟DOM。 所以除了使用"传统"的模板来构建 UI,也可使用 render
函数,借助于 JavaScript
的 power,开发者能够更灵活的控制 UI。前端
Vue 推荐在绝大多数状况下使用 template 来建立你的 HTML。然而在一些场景中,你真的须要 JavaScript 的彻底编程的能力,这就是 render 函数,它比 template 更接近编译器。vue
于 React 栈(React & React Native)的开发者而言, render 函数再熟悉不过了(毕竟 React 中只能经过 render 函数来声明 UI) 。那是否是说明,咱们能够用相似的方式来实现 Vue 组件。别高兴太早,咱们先来看看官网给的例子吧。git
这是什么鬼?render
函数里竟然不是熟悉的 jsx
,而是调用了很是原始的 createElement
函数。这个本没有什么毛病,毕竟刚入门 React 时,是用的这个函数,且对于比较简单的UI ,这个函数足以胜任。咱们再来看一个稍微复杂一些的没有使用 jsx
的 render
函数。github
看到这样的 render
函数, 有没有很怀念简洁可读的jsx
,没有的话,那大概是个大神或者自虐狂吧😂。npm
虽然Vue并无提供开箱即用的jsx
支持,但其提供了babel 插件,让咱们也能像 React 同样(90%类似)使用 jsx 来 构建 UI。废话说了一堆,是时候开始 vue jsx 之旅了。开始以前先对比下两种表格的使用方式。编程
<el-table
:data="tableData"
style="width: 100%">
<el-table-column prop="date" label="日期" width="180"></el-table-column>
<el-table-column prop="name" label="姓名" width="180"></el-table-column>
<el-table-column prop="address" label="地址"></el-table-column>
</el-table>
复制代码
iview 中的 Tablebash
<Table :columns="columns1" :data="data1"></Table>
复制代码
直观的看来,第二种更符合数据驱动的思想。固然咱们在这里并不比较这两种方式孰优孰劣,仁者见仁智者见智。那么是否有办法也让 element ui 中的 table 也支持第二种方式呢?办法确定是有的。用jsx
写组件应该再合适不过了,下面咱们就以 jsx 的方式来对 el-table 进行二次封装,前端框架
<table-panel
showHeaderAction
:data="taskList"
:totalSize="totalSize"
:columns="tableColumns"
:onPageChange="handlePageChange"
:onHeaderNew="handleOpenEditPage"
/>
复制代码
npm install\
babel-plugin-syntax-jsx\
babel-plugin-transform-vue-jsx\
babel-helper-vue-jsx-merge-props\
babel-preset-env\
--save-dev
复制代码
npm i babel-plugin-jsx-v-model -D
复制代码
这个模块是可选的,可让
jsx
支持v-model
指令
.babelrc
文件){
"presets": ["env"],
"plugins": ["jsx-v-model", "transform-vue-jsx"]
}
复制代码
注,在实现该组件过程当中,并无直接继承 ElTable,而是利用
inheritAttrs
特性,父组件上非父props
的属性都会回退到 dom 上的 attr 属性,组件内能够经过this.$attrs
获取到这些属性,并传递给ElTable 达到"继承"ElTable 的效果。
在上面的render
函数中,咱们用了三个函数分别来返回 Header, Body, 以及 Footer 来组合咱们的新组件。咱们先来看下renderPanelBody
函数中究竟是什么东西
renderPanelBody(h) {
const props = {
props: this.$attrs,
};
const on = {
on: this.$listeners,
};
const { body } = this.$slots;
if (body) return body.map(item => item);
return (
<el-table
ref="table"
{...props}
{...on}
>
{
this.columns.map(item => this.renderTableColumn(h, item))
}
</el-table>
);
},
复制代码
首先咱们先建立了一个attributes
来保存非父组件上的 props
以及Events
,而后使展开运算符就能够将 attributes
注入到el-table
组件内,经过this.slots对象中的每一个key均是
Array
类型,所以须要 map 再返回。若是用户没有自定义内容,则返回el-table
。注意到 el-table
的内容咱们经过map
外面传递进来的 columns
属性来生成el-table
中内置的 el-table-column
。
renderTableColumn(h, colOptions) {
// 兼容 iview 表格的部分配置
colOptions.prop = colOptions.key || colOptions.prop;
colOptions.label = colOptions.title || colOptions.label;
const props = {
props: colOptions,
};
const { render } = colOptions;
const slotScope = {
scopedSlots: {
default(scope) {
return typeof render === 'function' ? render(h, scope)
: scope.row[colOptions.prop];
},
},
};
return (
<el-table-column
{...props}
{...slotScope}
>
</el-table-column>
);
},
复制代码
colums 表格列配置
[
{
title: '操做人',
key: 'operatorId',
minWidth: 120,
render?
},
]
复制代码
由renderPanelBody
中能够知道,renderTableColumn
中的第二个参数为用户传进来的表格列配置数组中的一项,它应该由 el-table-column
的 props 构成。对于熟悉的同窗可能知道原生的自定义内容是经过 slot-scope
的方式实现的,而经过配置的方式,咱们只能给每一列配置一个render
函数来实现自定义内容。怎么才能让这二者等价呢?
<el-table-column prop="enableStatus" label="商品状态" min-width="140">
<template slot-scope="scope">
<span>{{getDisplayName(statusDropdown, scope.row.enableStatus)}}</span>
</template>
</el-table-column>
复制代码
查看vue文档得知,每一个vue 实例上存在一个$scopedSlots
的属性,它能够访问做用域插槽。这和咱们要实现的需求不谋而和。
scopedSlots 在使用渲染函数开发一个组件时特别有用**。
所以咱们能够根据 外面配置的 render 函数来自定义默认做用域插槽的内容了。
const slotScope = {
scopedSlots: {
default(scope) {
return typeof render === 'function' ? render(h, scope)
: scope.row[colOptions.prop];
},
},
};
复制代码
写到这里,咱们基于 jsx 的 组件二次封装也就完成了。总体下来,如下几点须要注意
v-if
指令能够用 if
条件语句实现;v-for
指令能够用 数组map
循环来实现;v-model
指令可使用 babel-plugin-jsx-v-model
插件;:key="value"
)使用 key={this.value}
的方式来实现其余 Vue jsx 相关的问题均可以在babel-plugin-transform-vue-jsx及其相关 issue 中找到答案。