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 中找到答案。