el-table高度自适应踩坑记:为什么mounted里计算无效?

0 阅读2分钟

问题

我们的项目是一个后台管理系统,其中涉及到大量的表格,用的技术是el-table,为了让页面总是能按一屏的高度展示,所以el-table这个高度可以变化的组件就适合来做页面的自适应伸展,主要是通过动态计算El-table的高度,将页面下边的空白。

页面的结构如下:

<template>
  <div>
    <el-container>
      <el-header class="header"> Header </el-header>
      <el-container>
        <el-container>
          <el-aside width="200px" class="aside">Aside</el-aside>
          <el-main class="main">
            <el-header class="header">有时候有有时候没有 </el-header>
            <el-table :height="tableHeight" :data="tableData" style="width: 100%">
              <el-table-column prop="date" label="Date" width="180" />
              <el-table-column prop="name" label="Name" width="180" />
              <el-table-column prop="address" label="Address" />
            </el-table>
            <el-pagination layout="prev, pager, next" :total="50" />
          </el-main>
        </el-container>
        <el-footer class="footer">Footer</el-footer>
      </el-container>
    </el-container>
  </div>
</template>

<script>
  export default {
    name: 'HelloWorld',
    props: {
      msg: String,
    },
    data() {
      return {
        tableHeight: 300,
        tableData: [],
      }
    },
    mounted() {
      const HEADER_HEIGHT = 60
      const HEADER_INNER_HEIGHT = 60
      const FOOTER_HEIGHT = 60
      const PAGINATION_HEIGHT = 32
      this.tableHeight = 
          window.innerHeight - 
          HEADER_HEIGHT - 
          HEADER_INNER_HEIGHT - 
          FOOTER_HEIGHT - 
          PAGINATION_HEIGHTconsole.log(this.tableHeight)
    },
  }
</script>

<style scoped>
  .header {
    background-color: aquamarine;
  }
  .aside {
    height: calc(100vh - 120px);
    background-color: blueviolet;
  }
  .main {
    border: 1px solid gray;
  }
  .footer {
    position: fixed;
    width: 100%;
    bottom: 0;
    background-color: brown;
  }
</style>

image.png

为了让页面始终展示一屏,就要动态的去计算el-table的高度。如果只是将高度的计算放在mounted中,就会发现并不起作用。这是为什么呢?

这就涉及到el-table的height到底是怎么用的问题了,我们来看下height的用法,通过查看element-ui的packages\table\src\table.vue文件,我们可以找到唯一使用props中传递的height的地方是在watch中:

height: {
    immediate: true,
    handler(value) {
      this.layout.setHeight(value);
    }
},

在这里,当height通过props传递时,会立即执行一次

this.layout.setHeight(value)

在packages\table\src\table-layout.js文件中,这个方法中核心代码如下:

  setHeight(value, prop = 'height') {
    const el = this.table.$el;
    this.height = value;
    
    // 这里是在mounted中修改height不生效的关键原因,第一次调用setHeight在watch的immediate中,
    // 也就是created阶段,但是此时dom还没有创建,所以setHeight被放在了nextTick中。
    // 当第二次调用也就是在mounted中修改高度,触发了高度的watch,此时dom已创建,所以同步执行高度赋值
    if (!el && (value || value === 0)) return Vue.nextTick(() => this.setHeight(value, prop));

    if (typeof value === 'number') {
      el.style[prop] = value + 'px';
      this.updateElsHeight();
    } else if (typeof value === 'string') {
      el.style[prop] = value;
      this.updateElsHeight();
    }
  }

先取得table.$el,也就是

<div class="el-table">

这是el-table渲染到页面上的最外层div,将这个div的height设置成value。最后调用updateElsHeight(),这个函数里面主要是对子元素的高度进行了更新。

所以,理论上说,只要我们在代码中对el-table的高度进行了更改,el-table就会自动响应,那为什么在mounted中写的计算并没有生效呢?

原理

这个关键点就是我在上面setHeight注释中写的:

// 这里是在mounted中修改height不生效的关键原因,第一次调用setHeight在watch的immediate中,
// 也就是created阶段,但是此时dom还没有创建,所以setHeight被放在了nextTick中。
// 当第二次调用也就是在mounted中修改高度,触发了高度的watch,此时dom已创建,所以同步执行高度赋值
if (!el && (value || value === 0)) return Vue.nextTick(() => this.setHeight(value, prop));

由此我们可以看出,只要我们可以保证,在create阶段创建的nextTick执行完毕之后,再去修改表格的高度,表格就会立即响应,为了达到这个效果,我们的代码必须写到另一个nextTick中,这样才能保证它排序在create创建的nextTick之后。我们来改下代码:

mounted() {
  const HEADER_HEIGHT = 60
  const HEADER_INNER_HEIGHT = 60
  const FOOTER_HEIGHT = 60
  const PAGINATION_HEIGHT = 32
  this.$nextTick(() => {
    this.tableHeight =
      window.innerHeight -
      HEADER_HEIGHT -
      HEADER_INNER_HEIGHT -
      FOOTER_HEIGHT -
      PAGINATION_HEIGHT
    console.log(this.tableHeight)
  })
},

刷新页面,就可以看到表格高度正确展示了

image.png

为了自适应页面大小,可以加上,resize的事件监听:

methods: {
  setTableHeight() {
    const HEADER_HEIGHT = 60
    const HEADER_INNER_HEIGHT = 60
    const FOOTER_HEIGHT = 60
    const PAGINATION_HEIGHT = 32
    this.tableHeight =
      window.innerHeight -
      HEADER_HEIGHT -
      HEADER_INNER_HEIGHT -
      FOOTER_HEIGHT -
      PAGINATION_HEIGHT
    console.log(this.tableHeight)
  },
},
mounted() {
  this.$nextTick(() => this.setTableHeight())
  window.addEventListener('resize', this.setTableHeight)
},

这样就完美的完成了这个功能。

演示代码: issue-resolve/src/components/TableDemo.vue at main · crane1/issue-resolve