前端

Element UI table组件部分源码解读(store部分

2021-05-11  本文已影响0人  若年
image.png

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/index.js中

import Watcher from './watcher';

看一下watcher.js

export default Vue.extend({
  data() {
    return {
      states: {
        // 3.0 版本后要求必须设置该属性
        rowKey: null,

        // 渲染的数据来源,是对 table 中的 data 过滤排序后的结果
        data: [],

        // 是否包含固定列
        isComplex: false,

        // 列
        _columns: [], // 不可响应的
        originColumns: [],
        columns: [],
        fixedColumns: [],
        rightFixedColumns: [],
        leafColumns: [],
        fixedLeafColumns: [],
        rightFixedLeafColumns: [],
        leafColumnsLength: 0,
        fixedLeafColumnsLength: 0,
        rightFixedLeafColumnsLength: 0,

        // 选择
        isAllSelected: false,
        selection: [],
        reserveSelection: false,
        selectOnIndeterminate: false,
        selectable: null,

        // 过滤
        filters: {}, // 不可响应的
        filteredData: null,

        // 排序
        sortingColumn: null,
        sortProp: null,
        sortOrder: null,

        hoverRow: null
      }
    };
  },

  mixins: [expand, current, tree],

  methods: {
    // 检查 rowKey 是否存在
    assertRowKey() {
      const rowKey = this.states.rowKey;
      if (!rowKey) throw new Error('[ElTable] prop row-key is required');
    },

    // 更新列
    updateColumns() {
      const states = this.states;
      const _columns = states._columns || [];
      states.fixedColumns = _columns.filter((column) => column.fixed === true || column.fixed === 'left');
      states.rightFixedColumns = _columns.filter((column) => column.fixed === 'right');

      if (states.fixedColumns.length > 0 && _columns[0] && _columns[0].type === 'selection' && !_columns[0].fixed) {
        _columns[0].fixed = true;
        states.fixedColumns.unshift(_columns[0]);
      }

      const notFixedColumns = _columns.filter(column => !column.fixed);
      states.originColumns = [].concat(states.fixedColumns).concat(notFixedColumns).concat(states.rightFixedColumns);

      const leafColumns = doFlattenColumns(notFixedColumns);
      const fixedLeafColumns = doFlattenColumns(states.fixedColumns);
      const rightFixedLeafColumns = doFlattenColumns(states.rightFixedColumns);

      states.leafColumnsLength = leafColumns.length;
      states.fixedLeafColumnsLength = fixedLeafColumns.length;
      states.rightFixedLeafColumnsLength = rightFixedLeafColumns.length;

      states.columns = [].concat(fixedLeafColumns).concat(leafColumns).concat(rightFixedLeafColumns);
      states.isComplex = states.fixedColumns.length > 0 || states.rightFixedColumns.length > 0;
    },

    // 更新 DOM
    scheduleLayout(needUpdateColumns) {
      if (needUpdateColumns) {
        this.updateColumns();
      }
      this.table.debouncedUpdateLayout();
    },

    // 选择
    isSelected(row) {
      const { selection = [] } = this.states;
      return selection.indexOf(row) > -1;
    },

    clearSelection() {
      const states = this.states;
      states.isAllSelected = false;
      const oldSelection = states.selection;
      if (oldSelection.length) {
        states.selection = [];
        this.table.$emit('selection-change', []);
      }
    },

    cleanSelection() {
      const states = this.states;
      const { data, rowKey, selection } = states;
      let deleted;
      if (rowKey) {
        deleted = [];
        const selectedMap = getKeysMap(selection, rowKey);
        const dataMap = getKeysMap(data, rowKey);
        for (let key in selectedMap) {
          if (selectedMap.hasOwnProperty(key) && !dataMap[key]) {
            deleted.push(selectedMap[key].row);
          }
        }
      } else {
        deleted = selection.filter(item => data.indexOf(item) === -1);
      }
      if (deleted.length) {
        const newSelection = selection.filter(item => deleted.indexOf(item) === -1);
        states.selection = newSelection;
        this.table.$emit('selection-change', newSelection.slice());
      }
    },

    toggleRowSelection(row, selected, emitChange = true) {
      const changed = toggleRowStatus(this.states.selection, row, selected);
      if (changed) {
        const newSelection = (this.states.selection || []).slice();
        // 调用 API 修改选中值,不触发 select 事件
        if (emitChange) {
          this.table.$emit('select', newSelection, row);
        }
        this.table.$emit('selection-change', newSelection);
      }
    },

    _toggleAllSelection() {
      const states = this.states;
      const { data = [], selection } = states;
      // when only some rows are selected (but not all), select or deselect all of them
      // depending on the value of selectOnIndeterminate
      const value = states.selectOnIndeterminate
        ? !states.isAllSelected
        : !(states.isAllSelected || selection.length);
      states.isAllSelected = value;

      let selectionChanged = false;
      data.forEach((row, index) => {
        if (states.selectable) {
          if (states.selectable.call(null, row, index) && toggleRowStatus(selection, row, value)) {
            selectionChanged = true;
          }
        } else {
          if (toggleRowStatus(selection, row, value)) {
            selectionChanged = true;
          }
        }
      });

      if (selectionChanged) {
        this.table.$emit('selection-change', selection ? selection.slice() : []);
      }
      this.table.$emit('select-all', selection);
    },

    updateSelectionByRowKey() {
      const states = this.states;
      const { selection, rowKey, data } = states;
      const selectedMap = getKeysMap(selection, rowKey);
      data.forEach(row => {
        const rowId = getRowIdentity(row, rowKey);
        const rowInfo = selectedMap[rowId];
        if (rowInfo) {
          selection[rowInfo.index] = row;
        }
      });
    },

    updateAllSelected() {
      const states = this.states;
      const { selection, rowKey, selectable } = states;
      // data 为 null 时,解构时的默认值会被忽略
      const data = states.data || [];
      if (data.length === 0) {
        states.isAllSelected = false;
        return;
      }

      let selectedMap;
      if (rowKey) {
        selectedMap = getKeysMap(selection, rowKey);
      }
      const isSelected = function(row) {
        if (selectedMap) {
          return !!selectedMap[getRowIdentity(row, rowKey)];
        } else {
          return selection.indexOf(row) !== -1;
        }
      };
      let isAllSelected = true;
      let selectedCount = 0;
      for (let i = 0, j = data.length; i < j; i++) {
        const item = data[i];
        const isRowSelectable = selectable && selectable.call(null, item, i);
        if (!isSelected(item)) {
          if (!selectable || isRowSelectable) {
            isAllSelected = false;
            break;
          }
        } else {
          selectedCount++;
        }
      }

      if (selectedCount === 0) isAllSelected = false;
      states.isAllSelected = isAllSelected;
    },

    // 过滤与排序
    updateFilters(columns, values) {
      if (!Array.isArray(columns)) {
        columns = [columns];
      }
      const states = this.states;
      const filters = {};
      columns.forEach(col => {
        states.filters[col.id] = values;
        filters[col.columnKey || col.id] = values;
      });

      return filters;
    },

    updateSort(column, prop, order) {
      if (this.states.sortingColumn && this.states.sortingColumn !== column) {
        this.states.sortingColumn.order = null;
      }
      this.states.sortingColumn = column;
      this.states.sortProp = prop;
      this.states.sortOrder = order;
    },

    execFilter() {
      const states = this.states;
      const { _data, filters } = states;
      let data = _data;

      Object.keys(filters).forEach((columnId) => {
        const values = states.filters[columnId];
        if (!values || values.length === 0) return;
        const column = getColumnById(this.states, columnId);
        if (column && column.filterMethod) {
          data = data.filter((row) => {
            return values.some(value => column.filterMethod.call(null, value, row, column));
          });
        }
      });

      states.filteredData = data;
    },

    execSort() {
      const states = this.states;
      states.data = sortData(states.filteredData, states);
    },

    // 根据 filters 与 sort 去过滤 data
    execQuery(ignore) {
      if (!(ignore && ignore.filter)) {
        this.execFilter();
      }
      this.execSort();
    },

    clearFilter(columnKeys) {
      const states = this.states;
      const { tableHeader, fixedTableHeader, rightFixedTableHeader } = this.table.$refs;

      let panels = {};
      if (tableHeader) panels = merge(panels, tableHeader.filterPanels);
      if (fixedTableHeader) panels = merge(panels, fixedTableHeader.filterPanels);
      if (rightFixedTableHeader) panels = merge(panels, rightFixedTableHeader.filterPanels);

      const keys = Object.keys(panels);
      if (!keys.length) return;

      if (typeof columnKeys === 'string') {
        columnKeys = [columnKeys];
      }

      if (Array.isArray(columnKeys)) {
        const columns = columnKeys.map(key => getColumnByKey(states, key));
        keys.forEach(key => {
          const column = columns.find(col => col.id === key);
          if (column) {
            // TODO: 优化这里的代码
            panels[key].filteredValue = [];
          }
        });
        this.commit('filterChange', {
          column: columns,
          values: [],
          silent: true,
          multi: true
        });
      } else {
        keys.forEach(key => {
          // TODO: 优化这里的代码
          panels[key].filteredValue = [];
        });

        states.filters = {};
        this.commit('filterChange', {
          column: {},
          values: [],
          silent: true
        });
      }
    },

    clearSort() {
      const states = this.states;
      if (!states.sortingColumn) return;

      this.updateSort(null, null, null);
      this.commit('changeSortCondition', {
        silent: true
      });
    },

    // 适配层,expand-row-keys 在 Expand 与 TreeTable 中都有使用
    setExpandRowKeysAdapter(val) {
      // 这里会触发额外的计算,但为了兼容性,暂时这么做
      this.setExpandRowKeys(val);
      this.updateTreeExpandKeys(val);
    },

    // 展开行与 TreeTable 都要使用
    toggleRowExpansionAdapter(row, expanded) {
      const hasExpandColumn = this.states.columns.some(({ type }) => type === 'expand');
      if (hasExpandColumn) {
        this.toggleRowExpansion(row, expanded);
      } else {
        this.toggleTreeExpansion(row, expanded);
      }
    }
  }
});

Watcher实际上是利用vue构造出的一个子类,提供了state状态包括列数据、选择数据、过滤数据、排序数据等,并且提供了一些列数据处理的方法(排序,过滤等)。

 mixins: [expand, current, tree],

这里将这三个文件的内容通过mixin混入

current.js
current.js主要是处理current row变化的方法,row数据变更,以及事件的抛出。
expand.js
expand文件主要提供处理扩展行的几个方法
tree.js
tree.js主要是提供了树形数据的节点更新,序列化等方法

然后再看一下index.js

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方法保证数据流向的清晰

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) {};

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>

在data中创建store

this.store = createStore(this, {
    rowKey: this.rowKey,
    defaultExpandAll: this.defaultExpandAll,
    selectOnIndeterminate: this.selectOnIndeterminate,
    // TreeTable 的相关配置
    indent: this.indent,
    lazy: this.lazy,
    lazyColumnIdentifier: hasChildren,
    childrenColumnName: children
});
上一篇下一篇

猜你喜欢

热点阅读