源码
目录地址 packages\table
疑问
slot作用域插槽使用需要在子组件中命名属性才可以调用,而element-ui中,el-tabel-column 是如何封装slot-scope="scope",并且它是怎么调用的呢?
<el-table-column
fixed="right"
label="订单金额(元)"
prop=""
show-overflow-tooltip
width="120"
>
<template slot-scope="scope">
{{ scope.row.payAmount }}
</template>
</el-table-column>
slot作用域案列
//父组件
<template>
<todo-list :todos="todos">
<template slot-scope="slotProps">
<span v-if="slotProps.todo.isComplete">✓</span>
<span>{{slotProps.todo.text}}</span>
</template>
</todo-list>
</template>
//子组件
<template>
<ul>
<li v-for="todo in todos" :key="todo.id">
<slot :todo="todo">
</slot>
</li>
</ul>
</template>
源码展示
- table.vue 组件获取数据,存储到store
<table-body
fixed="right"
:store="store"
:stripe="stripe"
:row-class-name="rowClassName"
:row-style="rowStyle"
:highlight="highlightCurrentRow"
:style="{
width: bodyWidth
}">
</table-body>
data: {
immediate: true,
handler(value) {
this.store.commit('setData', value);
}
},
- 传入到store除了赋值,还进行了一系列相关操作,
如数据排序排序过滤、更新当前行数据,更新展示数据......
Watcher.prototype.mutations = {
setData(states, data) {
const dataInstanceChanged = states._data !== data;
states._data = data;
//数据排序排序过滤
this.execQuery();
// 数据变化,更新部分数据。
// 没有使用 computed,而是手动更新部分数据https://github.com/vuejs/vue/issues/6660#issuecomment-331417140
this.updateCurrentRowData();//新当前行数据
this.updateExpandRows();//更新展示数据
if (states.reserveSelection) {
this.assertRowKey();
this.updateSelectionByRowKey();
} else {
if (dataInstanceChanged) {
this.clearSelection();
} else {
this.cleanSelection();
}
}
this.updateAllSelected();//更新数据是否全选
this.updateTableScrollY();//滚动条
},
...
}
- table-body.js 为渲染函数组件,通过计算属性获取到store/data数据,并且根据渲染函数进行添加,遍历数据
computed: {
table() {
return this.$parent;
},
...mapStates({
data: 'data',
columns: 'columns',
treeIndent: 'indent',
leftFixedLeafCount: 'fixedLeafColumnsLength',
rightFixedLeafCount: 'rightFixedLeafColumnsLength',
columnsCount: states => states.columns.length,
leftFixedCount: states => states.fixedColumns.length,
rightFixedCount: states => states.rightFixedColumns.length,
hasExpandColumn: states => states.columns.some(({ type }) => type === 'expand')
}),
...
},
render(h) {
const data = this.data || [];
return (
<table
class="el-table__body"
cellspacing="0"
cellpadding="0"
border="0">
<colgroup>
{
this.columns.map(column => <col name={column.id} key={column.id} />)
}
</colgroup>
<tbody>
{
data.reduce((acc, row) => {
return acc.concat(this.wrappedRowRender(row, acc.length));
}, [])
}
<el-tooltip effect={this.table.tooltipEffect} placement="top" ref="tooltip" content={this.tooltipContent}></el-tooltip>
</tbody>
</table>
);
},
wrappedRowRender()函数 把 table-column.js渲染函数 当前row中的数据进行输出
该图的 tr 指向的就是tabel-row this.rowRender(row, $index)
rowRender(row, $index, treeRowData) {
const { treeIndent, columns, firstDefaultColumnIndex } = this;
const rowClasses = this.getRowClass(row, $index);
let display = true;
if (treeRowData) {
rowClasses.push('el-table__row--level-' + treeRowData.level);
display = treeRowData.display;
}
// 指令 v-show 会覆盖 row-style 中 display
// 使用 :style 代替 v-show https://github.com/ElemeFE/element/issues/16995
let displayStyle = display ? null : {
display: 'none'
};
return (
<TableRow
style={[displayStyle, this.getRowStyle(row, $index)]}
class={rowClasses}
key={this.getKeyOfRow(row, $index)}
nativeOn-dblclick={($event) => this.handleDoubleClick($event, row)}
nativeOn-click={($event) => this.handleClick($event, row)}
nativeOn-contextmenu={($event) => this.handleContextMenu($event, row)}
nativeOn-mouseenter={_ => this.handleMouseEnter($index)}
nativeOn-mouseleave={this.handleMouseLeave}
columns={columns}
row={row}
index={$index}
store={this.store}
context={this.context || this.table.$vnode.context}
firstDefaultColumnIndex={firstDefaultColumnIndex}
treeRowData={treeRowData}
treeIndent={treeIndent}
columnsHidden={this.columnsHidden}
getSpan={this.getSpan}
getColspanRealWidth={this.getColspanRealWidth}
getCellStyle={this.getCellStyle}
getCellClass={this.getCellClass}
handleCellMouseEnter={this.handleCellMouseEnter}
handleCellMouseLeave={this.handleCellMouseLeave}
isSelected={this.store.isSelected(row)}
isExpanded={this.store.states.expandRows.indexOf(row) > -1}
fixed={this.fixed}
>
</TableRow>
);
},
- table-row.js 文件 定义了相关的sloct-scope 值
...
const data = {
store,
isSelected,
isExpanded,
_self: context,
column: columnData,
row,
$index
};
...
return (
<td
style={this.getCellStyle($index, cellIndex, row, column)}
class={this.getCellClass($index, cellIndex, row, column)}
rowspan={rowspan}
colspan={colspan}
on-mouseenter={($event) => this.handleCellMouseEnter($event, row)}
on-mouseleave={this.handleCellMouseLeave}
>
{
column.renderCell.call(
this._renderProxy,
this.$createElement,
data,
columnsHidden[cellIndex]
)
}
</td>
);
...
- table-column.js 展示this.$scopedSlots.default 数据
...
let originRenderCell = column.renderCell;
// TODO: 这里的实现调整
if (column.type === 'expand') {
// 对于展开行,renderCell 不允许配置的。在上一步中已经设置过,这里需要简单封装一下。
column.renderCell = (h, data) => (<div class="cell">
{ originRenderCell(h, data) }
</div>);
this.owner.renderExpanded = (h, data) => {
return this.$scopedSlots.default
? this.$scopedSlots.default(data)
: this.$slots.default;
};
} else {
originRenderCell = originRenderCell || defaultRenderCell;
// 对 renderCell 进行包装
column.renderCell = (h, data) => {
let children = null;
if (this.$scopedSlots.default) {
children = this.$scopedSlots.default(data);
} else {
children = originRenderCell(h, data);
}
const prefix = treeCellPrefix(h, data);
const props = {
class: 'cell',
style: {}
};
if (column.showOverflowTooltip) {
props.class += ' el-tooltip';
props.style = {width: (data.column.realWidth || data.column.width) - 1 + 'px'};
}
return (<div { ...props }>
{ prefix }
{ children }
</div>);
};
}
return column;
...
- 输出scope的值
总结
- table.vue 组件获取数据data,并且存储到store
- 调用store 方法时进行相关表格方法调用实现相关数据整合
- table-body获取到数据,并且遍历到table-row,(此时的数据已进行了数据整合),整合后展示到table-column中
- table-column 主要通过this.slots.default 方法拿取相关数据和slot数据