美文网首页前端
Element UI table组件部分源码解读(store部分

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

作者: 若年 | 来源:发表于2021-05-11 13:55 被阅读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
    });
    

    相关文章

      网友评论

        本文标题:Element UI table组件部分源码解读(store部分

        本文链接:https://www.haomeiwen.com/subject/klmgdltx.html