Vue優雅設計一個組件javascript
本組件已開源,已npm publish,可自行npm install km-grid。css
注:雖然如今已經出現大量優秀的UI框架,如elementUI,iviewUI,單從業務、功能及性能仍不能知足項目全部需求,這時,就要考慮如何設計適合本身的組件。
組件設計其實就是模塊的設計。我把它分爲4個部分:UI設計,基本功能,業務拓展需求,性能。如下我已我本身設計的高性能table爲例,剖析我是如何優雅的設計一個組件。前端
- UI設計交互就比如模塊的import、exports,要保持統一的出口。即統一風格配色,統一交互。
- 基本功能就比如模塊的基本做用。必須保證基本功能的同時,兼容其餘。
- 業務拓展就比如模塊間的集成,這就要求組件可依耐性低、可拔插集成、高聚低耦合。
- 性能要考慮到大量數據或大量計算與GUI渲染線程互斥致使的性能問題。
注:一個組件的設計先從基本功能開始。vue
先看table的基本功能包括:頭部、序號列、多選列、左右固定列、總計行;
業務拓展功能:列搜索。java
考慮到左右固定列須要單獨的頭部、搜索行、內容、總計行。可將km-grid設計爲3個單獨的table,每一個單獨的table都是一個獨立的km-grid-item。web
![]()
<div :class="cls" :style="tableStyles">
<km-grid-item v-if="fixedLeftCol&&fixedLeftCol.length" fixed="left" v-on="$listeners" :columns="fixedLeftCol" :header-styles="leftFixedHeaderStyles" :body-styles="leftFixedBodyStyles"></km-grid-item>
<km-grid-item v-on="$listeners" :columns="centerCol" :expandColumn="expandCol" :header-styles="headerStyles" :body-styles="bodyStyles"></km-grid-item>
<km-grid-item v-if="fixedRightCol&&fixedRightCol.length" fixed="right" v-on="$listeners" :columns="fixedRightCol" :header-styles="rightFixedHeaderStyles" :body-styles="rightFixedBodyStyles"></km-grid-item>
</div>
複製代碼
這樣,整個table的設計便會輕便且簡潔。npm
單個table的設計又怎麼辦呢?
那我是如何在單個組件驅動其餘組件的呢? 編程
mounted () {
this.dispatch("KmGrid", "on-km-grid-item-add", this);
}
複製代碼
在km-grid接收:後端
created () {
this.$on('on-km-grid-item-add', item => {
this.itemVms.push(item);
})
}
複製代碼
接着在事件中觸發同步驅動整個table:api
HandleBodyScroll (event) {
this.$refs.header.scrollLeft = event.target.scrollLeft
if (this.$parent.itemVms && this.$parent.itemVms.length > 0) {
this.$parent.itemVms.forEach(v => {
let scrollDom = v.$el.querySelectorAll('.km-grid-noscroll')[0]
if (scrollDom) {
scrollDom.scrollTop = event.target.scrollTop
}
})
}
}
複製代碼
業務拓展需求有:根據行數據顯示不一樣按鈕,不一樣行爲,數據格式化,行下拉動態渲染。
把需求轉化爲數據操做就是先獲取行數據,根據行數據展現不一樣的按鈕,而後自定義行爲在業務中編寫:
先定義column及操做:
{
title: '操做',
type: 'handle',
handle: [
{
type: 'edit'
},
{
type: 'warehouse',
icon: 'km-stock',
click: row => {
this.$refs.listpage.showList = false
this.$refs.WarehouseInfo.loadEntity(row.id)
}
}
],
width: 90,
align: 'left',
fixed: 'right'
}
複製代碼
根據行數據渲染:
showCustomOperate: {
warehouse: row => {
switch (row.type) {
case '4':
return true
default:
return false
}
}
}
RebuildTableDataByColOperate (columns, data) {
data.forEach(v => {
handles.forEach(handle => {
v['__' + handle.type] =
this.showCustomOperate[handle.type](v) || false
})
})
}
HandleIconClick (item, row) {
return item.click(row);
}
複製代碼
經過這種集成方式組件沒必要關心業務需求,自動化配置自定義按鈕及操做行爲,可拔插的集成,避免影響平臺組件維護。
首先在columns裏配置:
{
type: 'expand',
width: 50,
render: (h, params) => {
return h(inventoryControl, {
props: {
row: params.row
}
})
}
}
components: {
inventoryControl: () => import('@/view/main-components/inventoryControl.vue')
}
複製代碼
而後在km-grid-item的tr裏面根據行數據動態render:
<div v-if="!fixed&&expandColumn.render" :class="cls+'-tr-expand'">
<div style="width:100%" v-if="row._clicked">
<td-render :row="row" :render="expandColumn.render"></td-render>
</div>
</div>
複製代碼
經過這種集成方式組件沒必要關心業務需求,返回行數據讓業務本身交互,不會污染當前組件DOM及多餘的代碼邏輯。
數據格式化是一個老生常談的問題,例以下面咱們只用經過這個方法便可格式化系統數據:
GetTdData (row, col) {
if (col.enumData) {
let desc = (col.desc && row[col.desc(row)]) ? '(' + row[col.desc(row)] + ')' : ''
return col.desc ? col.enumData[row[col.key]] + desc : col.enumData[row[col.key]]
} else if (col.valueKey) {
return row[col.key] ? `[${row[col.key]}]${row[col.valueKey]}` : ''
} else if (['date', 'datetime', 'time'].includes(col.type)) {
const dateEnum = { date: 'yyyy-MM-dd', datetime: 'yyyy-MM-dd HH:mm:ss', time: 'HH:mm:ss' }
if (!row[col.key]) {
return ''
} else {
return formatDate(row[col.key], col.format || dateEnum[col.type])
}
} else if (col.bizType && ['price', 'amt', 'qty', 'rate'].includes(col.bizType)) {
const formatEnum = { price: 4, amt: 2, qty: 3, rate: 4 }
if (typeOf(row[col.key]) === 'null') {
return '-'
} else {
return parseFloat(row[col.key]).toFixed(col.format || formatEnum[col.bizType])
}
}
return row[col.key]
},
GetTotalData (col) {
if (!col.total) {
return ''
} else if (col.totalValue) {
return col.totalValue
} else if (col.bizType && ['price', 'qty', 'amt'].includes(col.bizType)) {
const formatEnum = { price: 4, qty: 3, amt: 4 }
return this.$parent.data.map(row => Number(row[col.key])).reduce((a, b) => a + b, 0).toFixed(col.format || formatEnum[col.bizType])
}
return ''
}
複製代碼
這種默認配置是前端與後端約定好,前端在columns規定好,便可有序展開。
總結一下就是,拓展需求就是要可儘可能少的配置、自動化、可拔插、不污染組件、減小增長組件邏輯、無代碼侵入的支持業務功能。
經測試,km-grid擁有高效的性能併兼容主流瀏覽器(ie11以上,包含ie11)。對比了一樣的iview-table,element-table,在500條級數據時,km-grid表現更佳,大大超越iview-table。各位同窗可直接在npm install。
屬性名 | 屬性類型 | 描述 |
---|---|---|
data | Array | 元數據 |
headerRowHeight | Number | 頭部高度 |
rowHeight | Number | 行高度 |
totalRowHeight | Number | 總計高度 |
height | Number | 指定高度 |
width | Number | 指定寬度 |
border | Boolean | 顯示分割線 |
pageSize | Number | 分頁數據 |
current | Number | 分頁數據 |
draggable | Boolean | 是否可拖拽列寬 |
clickAsync | Number | 點擊事件是否異步加載 |
事件名 | 描述 |
---|---|
on-selection-change | 全選,單選時觸發 |
on-row-click | 點擊 |
on-row-dblclick | 雙擊 |
on-sort-change | 排序 |
方法名 | 描述 |
---|---|
GetSelectRows | 獲取勾選選行 |
ClearSelectRows | 清除溝選行 |
GetCurrentRow | 獲取選中行 |
km-grid在設計上精簡,使用v-on="$listeners"向上拋出事件,因此寫km-grid-item的emit事件不用重複的再往上面拋,而對於須要對外開放的API,km-grid則故意將須要開放的API寫在km-grid裏面。
// public API so i Expose in km-grid
GetSelectRows () {
return this.itemVms[0].GetSelectRows()
},
// public API so i Expose in km-grid
ClearSelectRows () {
this.data.forEach(row => {
this.$set(row, '_checked', false)
})
},
// public API so i Expose in km-grid
GetCurrentRow () {
return this.data.find(row => row._clicked)
}
複製代碼
如今的前端愈來愈後後端化,從npm倉庫到模塊化,自動化,前端早已不是幾年前的那個畫頁面前端。更多的考慮是模塊化組件的同時,處理好組件與平臺,平臺與業務這三方面的「交互」。若是你能很好的理解VUE的VueComponent響應式,將在VUE的海洋如魚得水,同時也對面向對象編程有本身獨到的看法。
下一期我將展現如何將km-grid即成爲高性能的可編輯雙向綁定table,同時也將開源。