Vue 作为日前最为火热的前端框架之一,其流行程度很大部分得益于对开发者友好。尤其是SFC(单文件组件)的模式深得人心,开发者通过在一个文件里同时书写模板,JS 逻辑以及样式,就能完成组件的封装,相比其他方式,组件更加内聚,便于维护。
在 Vue 2.0 版本之后,Vue 增加了 vdom
以及 render
函数等新特性, template 模板并不会直接生成真实的 dom, 而是先编译为 render
函数, 再由 render
函数 生成虚拟DOM。 因此除了使用"传统"的模板来构建 UI,也可以使用 render
函数,借助于 JavaScript
的 power,开发者可以更灵活的控制 UI。
Vue 推荐在绝大多数情况下使用 template 来创建你的 HTML。然而在一些场景中,你真的需要 JavaScript 的完全编程的能力,这就是 render 函数,它比 template 更接近编译器。
于 React 栈(React & React Native)的开发者而言, render 函数再熟悉不过了(毕竟 React 中只能通过 render 函数来声明 UI) 。那是不是说明,我们可以用类似的方式来实现 Vue 组件。别高兴太早,我们先来看看官网给的例子吧。
这是什么鬼?render
函数里居然不是熟悉的 jsx
,而是调用了非常原始的 createElement
函数。这个本没有什么毛病,毕竟刚入门 React 时,是用的这个函数,且对于比较简单的UI ,这个函数足以胜任。我们再来看一个稍微复杂一些的没有使用 jsx
的 render
函数。
看到这样的 render
函数, 有没有很怀念简洁可读的jsx
,没有的话,那大概是个大神或者自虐狂吧?。
虽然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>
<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"
{...on}
{...props}
>
{
this.columns.map(item => this.renderTableColumn(h, item))
}
</el-table>
);
},
首先我们先创建了一个attributes
来保存非父组件上的 props
以及Events
,然后使展开运算符就可以将 attributes
注入到el-table
组件内,通过this.$slots.body 拿到 <div slot='body'></div>
名字为 body 的具名 slot
,如果定义了这个 slot
,则直接返回用户定义的内容,由于 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, 用来访问作用域插槽。对于包括 默认 slot 在内的每一个插槽,该对象都包含一个返回相应 VNode 的函数。vm.$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 中找到答案。
扫码关注w3ctech微信公众号
共收到0条回复