el-tabel的header源码解读

142 阅读3分钟

前言

大家好,我是小欲望。上周同事问我数组变化了为什么el-tabel的header顺序还是老样子。

问题

直接上代码:

<template>
  <div>
    <el-table>
      <el-table-column
        v-for="item in arr"
        :key="item.key"
        :label="item.label"
      ></el-table-column>
    </el-table>
  </div>
</template>

<script>
export default {
  data() {
    return {
      arr: [
        {
          key: 1,
          label: '测试1',
        },
        {
          key: 2,
          label: '测试2',
        },
        {
          key: 3,
          label: '测试3',
        },
        {
          key: 4,
          label: '测试4',
        },
      ],
    };
  },
  mounted() {
    setTimeout(() => {
      this.arr.reverse()
      console.log(this.arr);
    },3000)
  },
};
</script>

看结果

change.gif

当我转换了arr数组但是未发生header的转变。

启动elememt调试

第一步

先下载elementui

有时候我们拉去项目会网络请求超时。一个小技巧。
github.com/ElemeFE/ele… 改成 git://github.com/ElemeFE/element.git 会提升下载速度。

第二步

image.png

直接执行npm run dev 它包含了安装所需要的包

image.png

当看到这个就知道运行起来了。

重要的事情说三遍

执行 http://localhost:8085/
执行 http://localhost:8085/
执行 http://localhost:8085/

第三步

image.png

这就是打开的页面了。

image.png

因为我是中文的,所以组件的例子代码就在这里面,是使用md格式写的。

寻找原因

这里就不说源码的对应的部分了。简单实现一个原因出来。

image.png

先看下目录结构,table是文件,store跟vuex差不多管理全局的数据。其他一一对应。

table

模仿tabel的自身

<template>
  <div>
    <Column v-for="item in arr" :key="item.key" :label="item" :store="store"></Column>
    <Header :store="store"></Header>
  </div>
</template>

<script>
import Column from './column'
import Header from './header'
import Store from './store'
export default {
  components: {
    Column,
    Header
  },
  data() {
    // 创建一个全局状态管理store每个组件都可以使用
    this.store = new Store()
    return {
      arr: [
         {
          key: 1,
          label: '测试1',
        },
        {
          key: 2,
          label: '测试2',
        },
        {
          key: 3,
          label: '测试3',
        },
        {
          key: 4,
          label: '测试4',
        },
      ],
      tableData: [],
    };
  },
  mounted() {
    setTimeout(() => {
      this.arr.reverse()
      console.log('this.arr', this.arr);
    }, 3000);
  },
};
</script>

column


export default {
  render(h) {
    h('div')
  },
  props: {
    column: {
      type: Object
    },
    store: {}
  },
  mounted() {
    // 把column储存起来
    this.store.commit('SET_ARR', this.column)
  },
  destroyed() {
    // 在全局中删除掉这些数据
    this.store.commit('SET_REMOVE', this.column)
  },
}

这就跟el-table-column,header的title就是这里的column下的label标签。组件mounted是执行commit(SET_ARR),把数据放到store.arr中去。当destroyed就删除store.arr对应的数据。

store/index

import Watcher from './watcher';

Watcher.prototype.mutations = {
  SET_ARR(state, name) {
    state.arr.push(name)
  },
  SET_REMOVE(state, name) {
    state.arr.splice(state.arr.indexOf(name), 1)
  },
}

// 给store的原型添加commit方法代理到原型的mutations的某个属性
Watcher.prototype.commit = function(name, ...args) {
  const mutations = this.mutations;
  if (mutations[name]) {
    //[this.states].concat(args) 这是因为apply的第二个参数是数组
    mutations[name].apply(this, [this.states].concat(args));
  } else {
    throw new Error(`Action not found: ${name}`);
  }
};

export default Watcher;

这个就跟vuex相似,绑定起来走commit就是执行mutations的某个属性函数。

watcher

import Vue from 'vue';

export default Vue.extend({
  data() {
    return {
      states: {
        // 3.0 版本后要求必须设置该属性
        arr: [],
      },
    };
  },
});

这个就是store,table所有的全局属性数据都存放在这里。这里就放一个arr。

header

export default {
  render(h) {
    return h('div', this.arr1.map(item => h('p', item.label)))
  },
  props: {
    store: {}
  },
  computed: {
    arr1() {
     return this.store.states.arr
    }
  }
}

头部渲染,从全局属性store.arr数据中拿到column存放的数据,就拿到了label数据循环渲染了出来。

原因

从这个代码中我们就知道header从store.arr拿值渲染,当column组件mounted往store.arr放数据,因为组件的销毁是与key有关(diff算法),因此顺序虽然变化,但是column是没有走destroyed,因此store.arr顺序就没有变化。

解决方案

因为组件是根据key来判断是否需要复用。所以我们只需要把key更换成不一样(例如添加一个Math.random())。所以我们只需要把key变成不一样就可以走column的destroyed,然后再mounted就重置了store.arr。这里就重新绘制了header,顺序就出来了。

最后

之前需要elementui的源码是有下载下来,但是一直都没有去看过,由于这次的原因,知道了看的流程与调试。还有就是源码中很多都是使用jsx和render函数来编写的,函数组件知识我还是比较欠缺。最后,希望文章对你有帮助。