最近在作vue高仿網易雲音樂的項目,在作的過程當中發現音樂表格這個組件會被很是多的地方複用,並且需求比較複雜的和靈活。vue
預覽地址node
源碼地址git
它須要支持:github
hideColumns參數, 自定義須要隱藏哪些列。element-ui
highLightText,傳入字符串,數據中命中的字符串高亮。bash
首先 看一下咱們日常的table寫法。babel
<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'],咱們須要在模板裏隱藏的話markdown
<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裏定義columnside
data() { return { columns: [{ prop: 'index', label: '', width: '50' }, { prop: 'artistsText', label: '歌手' }, { prop: 'albumName', label: '專輯' }, { prop: 'durationSecond', label: '時長', width: '100', }] } } 複製代碼
而後咱們在computed中計算hideColumns作一次合併oop
computed: { showColumns() { const { hideColumns } = this return this.columns.filter(column => { return !this.hideColumns.find((prop) => prop === column.prop) }) }, }, 複製代碼
那麼模板裏咱們就能夠簡寫成
<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中去,是一個很是簡便的方法。
這樣需求看似解決了,很美好。
可是咱們忘了很是重要的一點,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能夠說是複雜組件的終極解決方案。