最近在作vue高仿網易雲音樂的項目,在作的過程當中發現音樂表格這個組件會被很是多的地方複用,並且需求比較複雜的和靈活。vue
它須要支持:node
hideColumns參數, 自定義須要隱藏哪些列。git
highLightText,傳入字符串,數據中命中的字符串高亮。github
首先 看一下咱們日常的table寫法。element-ui
<el-table
:data="tableData"
style="width: 100%">
<el-table-column
prop="index"
label=" "
width="180">
</el-table-column>
<el-table-column
prop="name"
label="音樂標題"
width="180">
</el-table-column>
<el-table-column
prop="artistsText"
label="歌手">
</el-table-column>
</el-table>
複製代碼
這是官網的寫法,假設咱們傳入了 hideColumns: ['index', 'name'],咱們須要在模板裏隱藏的話```bash
<el-table
:data="tableData"
style="width: 100%">
<el-table-column
+++ v-if="!hideColumns.includes('index')"
prop="index"
label=" "
width="180">
</el-table-column>
<el-table-column
+++ v-if="!hideColumns.includes('name')"
prop="name"
label="音樂標題"
width="180">
</el-table-column>
<el-table-column
+++ v-if="!hideColumns.includes('address')"
prop="artistsText"
label="歌手">
</el-table-column>
</el-table>
複製代碼
這種代碼很是笨,因此咱們確定是接受不了的,咱們很天然的聯想到日常用v-for循環,能不能套用在這個需求上呢。 首先在data裏定義columnsbabel
data() {
return {
columns: [{
prop: 'index',
label: '',
width: '50'
}, {
prop: 'artistsText',
label: '歌手'
}, {
prop: 'albumName',
label: '專輯'
}, {
prop: 'durationSecond',
label: '時長',
width: '100',
}]
}
}
複製代碼
而後咱們在computed中計算hideColumns作一次合併ide
computed: {
showColumns() {
const { hideColumns } = this
return this.columns.filter(column => {
return !this.hideColumns.find((prop) => prop === column.prop)
})
},
},
複製代碼
那麼模板裏咱們就能夠簡寫成post
<el-table
:data="songs"
>
<template v-for="(column, index) in showColumns">
<el-table-column
:key="index"
// 混入屬性
v-bind="column"
>
</el-table-column>
</template>
</el-table>
複製代碼
注意v-bind="column"
這行, 至關於把column中的全部屬性混入到table-column中去,是一個很是簡便的方法。ui
這樣需求看似解決了,很美好。
可是咱們忘了很是重要的一點,slotScopes
這個東西!
好比音樂時長咱們須要format一下,
<el-table-column>
<template>
<span>{{ $utils.formatTime(scope.row.durationSecond) }}</span>
</template>
</el-table-column>
複製代碼
可是咱們如今把columns都寫到script裏了,和template分離開來了,我暫時還不知道有什麼方法能把sciprt
裏寫的模板放到template
裏用,因此先想到一個能夠解決問題的方法。就是在template里加一些判斷。
<el-table
v-bind="$attrs"
v-if="songs.length"
:data="songs"
@row-click="onRowClick"
:cell-class-name="tableCellClassName"
style="width: 99.9%"
>
<template v-for="(column, index) in showColumns">
<!-- 須要自定義渲染的列 -->
<el-table-column
v-if="['durationSecond'].includes(column.prop)"
:key="index"
v-bind="column"
>
<!-- 時長 -->
<template v-else-if="column.prop === 'durationSecond'">
<span>{{ $utils.formatTime(scope.row.durationSecond) }}</span>
</template>
</el-table-column>
<!-- 普通列 -->
<el-table-column
v-else
:key="index"
v-bind="column"
>
</el-table-column>
</template>
</el-table>
複製代碼
又一次的需求看似解決了,很美好。
雞你太美
這個歌名,咱們在搜索框輸入雞你
咱們須要把
<span>雞你太美</span>
複製代碼
轉化爲
<span>
<span class="high-light">雞你</span>
太美
</span>
複製代碼
咱們在template裏找到音樂標題這行,寫下這端代碼:
<template v-else-if="column.prop === 'name'">
<span>{{this.genHighlight(scope.row.name)}}</span>
</template>
複製代碼
methods: {
genHighlight(text) {
return <span>xxx</span>
}
}
複製代碼
我發現無從下手了, 由於jsx最終編譯成的是return vnode的方法,genHighlight執行之後返回的是vnode,可是你不能直接把vnode放到template裏去。
因此咱們要統一環境,直接使用jsx渲染咱們的組件,文檔能夠參照
babel-plugin-transform-vue-jsx
vuejs/jsx
import ElTable from 'element-ui/lib/table'
data() {
const commonHighLightSlotScopes = {
scopedSlots: {
default: (scope) => {
return (
<span>{this.genHighlight(scope.row[scope.column.property])}</span>
)
}
}
}
return {
columns: [{
prop: 'name',
label: '音樂標題',
...commonHighLightSlotScopes
}, {
prop: 'artistsText',
label: '歌手',
...commonHighLightSlotScopes
}, {
prop: 'albumName',
label: '專輯',
...commonHighLightSlotScopes
}, {
prop: 'durationSecond',
label: '時長',
width: '100',
scopedSlots: {
default: (scope) => {
return (
<span>{this.$utils.formatTime(scope.row.durationSecond)}</span>
)
}
}
}]
}
},
methods: {
genHighlight(title = '') {
...省去一些細節
const titleSpan = matchIndex > -1 ? (
<span>
{beforeStr}
<span class="high-light-text">{hitStr}</span>
{afterStr}
</span>
) : title;
return titleSpan;
},
},
render() {
const elTableProps = ElTable.props
// 從$attrs裏提取做爲prop的值
// 這裏要注意的點是駝峯命名法(camelCase)和短橫線命名法(kebab-case)
// 都是能夠被組件接受的,雖然elTable裏的prop定義的屬性叫cellClassName
// 可是咱們要把cell-class-name也解析進prop裏
const { props, attrs } = genPropsAndAttrs(this.$attrs, elTableProps)
const tableAttrs = {
attrs,
on: {
...this.$listeners,
['row-click']: this.onRowClick,
},
props: {
...props,
cellClassName: this.tableCellClassName,
data: this.songs,
},
style: { width: '99.9%' }
}
return this.songs.length ? (
<el-table
{...tableAttrs}
>
{this.showColumns.map((column, index) => {
const { scopedSlots, ...columnProps } = column
return (
<el-table-column key={index} props={columnProps} scopedSlots={scopedSlots} >
</el-table-column>
)
})}
</el-table>
) : null
}
複製代碼
attrs: this.$attrs
注意這句話,咱們在template裏能夠經過 v-bind="$attrs"
去透傳外部傳進來的全部屬性,
可是在jsx中咱們必須分類清楚傳給el-table的attrs
和props
好比el-table接受data
這個prop,若是你放在attrs裏傳進去,那麼就失效了。
這個我暫時也沒找到特別好的解決方法,只能先引用去拿elTable上的props去進行比對$attrs,取交集。
import ElTable from 'element-ui/lib/table'
// 從$attrs裏提取做爲prop的值
// 這裏要注意的點是駝峯命名法(camelCase)和短橫線命名法(kebab-case)
// 都是能夠被組件接受的,雖然elTable裏的prop定義的屬性叫cellClassName
// 可是咱們要把cell-class-name也解析進prop裏
const { props, attrs } = genPropsAndAttrs(this.$attrs, elTableProps)
複製代碼
最終代碼中模板的部分少了不少重複的判斷,維護性和擴展性都更強了,jsx能夠說是複雜組件的終極解決方案。