<el-tabel-column>标签是如何实现sloct-scope="scope"❓❓❓

402 阅读1分钟

源码

目录地址 packages\table

image.png

疑问

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>

源码展示

  1. 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);
    }
},
  1. 传入到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();//滚动条
},
...
  }
  1. 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中的数据进行输出 image.png

该图的 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>
      );
    },
  1. 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>
);
...
  1. 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;
...
  1. 输出scope的值

image.png

总结

  1. table.vue 组件获取数据data,并且存储到store
  2. 调用store 方法时进行相关表格方法调用实现相关数据整合
  3. table-body获取到数据,并且遍历到table-row,(此时的数据已进行了数据整合),整合后展示到table-column中
  4. table-column 主要通过this.scopedSlots.defaultthis.scopedSlots.default 或 this.slots.default 方法拿取相关数据和slot数据