最近有与业务需要,需要实现表格中固定列的功能,于是顺便研究了一下Element UI 的table组件,这里记录一下。
整体结构
话不多说,先看代码
- store文件夹:为table设计了一组私有的store数据,类似(vuex, redux),这个一会详细讲。
- config.js: 一些配置和默认信息,包括默认宽度之类的
- dropdown.js: 提供点击后产生dropdown的一些基础方法
- filter-panel.vue: 渲染过滤面板的
- layout-observer.js: 布局使用的一个Observer,里面提供了一些基础方法,主要包括两点:1.column变化时,动态更新显示宽度,2.table在进行滚动时,计算滚动位置。
- table-body, table-column, table-footer, table-header,这个四个顾名思义都是分别负责渲染对应的body,column,footer,header
- table-layout.js: 定义了一个TableLayout的基础类,内部建立了一个观察者模式。
- table.vue: 组合上面几个渲染模块,渲染整个table组件
- util.js: 一些工具方法
Store
熟悉vuex和redux的人都知道store的概念,由于表格数据比较复杂,element为table设计了一套私有的store便于管理,header, body等模块对store数据进行监听,table-store数据发生变化时,监听的模块自动更新渲染,这样使数据流向更清晰,也符合“数据驱动”的设计理念。
看一下store/index.js
import Watcher from './watcher';
这里首先引用了一个watcher,看看watcher做了什么:
export default Vue.extend({
data() {
return {
state:...
}
},
methods() {
// 检查 rowKey 是否存在
assertRowKey() {},
// 更新列
updateColumns() {
...
states.columns = ...;
},
// 更新 DOM
scheduleLayout(needUpdateColumns) {
if (needUpdateColumns) {
this.updateColumns();
}
this.table.debouncedUpdateLayout();
},
// 是否选中
isSelected(row) {},
// 清除选中
clearSelection() {},
// 清理选中信息(当数据实例发生改变时)
cleanSelection() {},
// 修改选中信息
toggleRowSelection(row, selected, emitChange = true) {},
// 修改全选信息
_toggleAllSelection() {},
// 根据rowkey更新选中信息
updateSelectionByRowKey() {},
// 更新全选信息
updateAllSelected() {},
// 更新过滤信息,过滤返回一个过滤器
updateFilters(columns, values) {},
// 更新排序信息
updateSort(column, prop, order) {},
// 根据过滤器, 执行过滤筛选操作
execFilter() {},
// 执行排序操作
execSort() {},
// 根据 filters 与 sort 去过滤 data
execQuery(ignore) {},
// 清理过滤器
clearFilter(columnKeys) {},
// 清理排序
clearSort() {},
// 适配层,expand-row-keys 在 Expand 与 TreeTable 中都有使用
setExpandRowKeysAdapter(val) {},
// 展开行与 TreeTable 都要使用
toggleRowExpansionAdapter(row, expanded) {}
}
})
好了这里知道Watcher实际上是利用vue构造出的一个子类,提供了state状态包括列数据、选择数据、过滤数据、排序数据等,并且提供了一些列数据处理的方法(排序,过滤等)。
再回到index文件:
Watcher.prototype.mutations = {
// 设置data数据
setData(states, data) {},
// 插入列 插入修改列数组,并通知table重新渲染
insertColumn(states, column, index, parent) {},
// 删除列 删除列数组信息,并通知table重新渲染
removeColumn(states, column, parent) {},
// 对列进行排序
sort(states, options) {},
// 修改排序条件
changeSortCondition(states, options) {},
// 修改过滤器
filterChange(states, options) {},
// 处理全选
toggleAllSelection() {},
// 处理行选中
rowSelectedChanged(states, row) {},
// 设置hover的行
setHoverRow(states, row) {},
// 设置当前行 调用current.js里面的方法
setCurrentRow(states, row) {}
};
Watcher.prototype.commit = function(name, ...args) {}
Watcher.prototype.updateTableScrollY = function() {}
export default Watcher;
这里为watcher添加了mutations的原型方法,熟悉store设计理念的都能理解,修改store数据必须要通过mutation方法保证数据流向的清晰。
current.js
current.js主要是处理current row变化的方法,row数据变更,以及事件的抛出。
export default {
data() {
return {
states: {
// 不可响应的,设置 currentRowKey 时,data 不一定存在,也许无法算出正确的 currentRow
// 把该值缓存一下,当用户点击修改 currentRow 时,把该值重置为 null
_currentRowKey: null,
currentRow: null
}
};
},
methods: {
// 设置当前行Key
setCurrentRowKey(key) {},
// 重置当前行key
restoreCurrentRowKey() {},
// 通过key找到当前行数据
setCurrentRowByKey(key) {},
// 更新当前行变化,抛出事件
updateCurrentRow(currentRow) {},
// 更新当前行变化的数据
updateCurrentRowData() {}
};
expand.js
expand文件主要提供处理扩展行的几个方法,分析如下
export default {
data() {
return {
states: {
defaultExpandAll: false,
expandRows: []
}
};
},
methods: {
// 更新扩展行信息
updateExpandRows() {},
// 控制是否显示扩展行,并抛出事件
toggleRowExpansion(row, expanded) {},
// 设置扩展行的数据和key
setExpandRowKeys(rowKeys) {},
// 判断是否为扩展行
isRowExpanded(row) {}
}
};
helper.js
helper这里理解为提供createStore, mapStates两个方法, createStore方法主要是创建store对象,而mapState提供了把state映射到实例数据上的功能。在table.vue中使用创建store
import Store from './index';
export function createStore(table, initialState = {}) {}
export function mapStates(mapper) {};
tree.js
tree.js主要是提供了树形数据的节点更新,序列化等方法,mixin到index文件中去,这里不具体展开。
最后看一下主要Table组件
table.vue是核心组件 简化后的模板是这样
// 隐藏列
<div class="hidden-columns" ref="hiddenColumns"><slot></slot></div>
// 表头部分
<div class="el-table__header-wrapper"><table-header></table-header></div>
// 主体部分
<div class="el-table__body-wrapper"><table-body></table-body></div>
// 占位块,没有数据时渲染
<div class="el-table__empty-block"></div>
// 插入至表格最后一行之后的内容 slot插入
<div class="el-table__append-wrapper"><slot="append"></slot></div>
// 表尾部分
<div class="el-table__footer-wrapper"><table-foot></table-foot></div>
// 左侧固定列部分
<div class="el-table__fixed"></div>
// 右侧固定列部分
<div class="el-table__fixed-right"></div>
// 右侧固定列部分补丁(滚动条)
<div class="el-table__fixed-right-patch"></div>
// 用于列宽调整的代理
<div class="el-table__column-resize-proxy"></div>
引用的文件包括:
//checkbox组件
import ElCheckbox from 'element-ui/packages/checkbox';
//节流,防抖
import { debounce, throttle } from 'throttle-debounce';
//resize的事件监听器
import { addResizeListener, removeResizeListener } from 'element-ui/src/utils/resize-event';
// 鼠标滚动处理方法
import Mousewheel from 'element-ui/src/directives/mousewheel';
// 多语言
import Locale from 'element-ui/src/mixins/locale';
// 低版本的移植提示
import Migrating from 'element-ui/src/mixins/migrating';
// 刚才提到的store数据
import { createStore, mapStates } from './store/helper';
// table布局方法
import TableLayout from './table-layout';
// 渲染Body
import TableBody from './table-body';
// 渲染表头
import TableHeader from './table-header';
// 渲染表尾
import TableFooter from './table-footer';
// px高度转换方法
import { parseHeight } from './util';
创建store数据在data部分:
this.store = createStore(this, {
rowKey: this.rowKey,
defaultExpandAll: this.defaultExpandAll,
selectOnIndeterminate: this.selectOnIndeterminate,
// TreeTable 的相关配置
indent: this.indent,
lazy: this.lazy,
lazyColumnIdentifier: hasChildren,
childrenColumnName: children
});
const layout = new TableLayout({
store: this.store,
table: this,
fit: this.fit,
showHeader: this.showHeader
});
return {
layout,
isHidden: false,
renderExpanded: null,
resizeProxyVisible: false,
resizeState: {
width: null,
height: null
},
// 是否拥有多级表头
isGroup: false,
scrollPosition: 'left'
};
最后在data中生成store对象,再初始化一个tableLayout对象,tableLayout提供了对整个表格宽度,高度等进行布局修改的方法,本篇文章不详细展开。