修改ElementUI源码实践

提要

github地址:https://github.com/boychina/element-devcss

Vue2.0+Vuex+ElementUI是如今不少项目都在使用的BS软件的开发组合。vue

Vue相较于Angular具备学习成本低,上手快以及组件轻量化的特色;相较于React,其官方提供的不少指令以及能够自定义的指令可以为让开发更加高效。而且相较于React生命周期监听全部props和state的变化,Vue中提供的watch方法监听单个数据的变化,可以更加直观的进行数据操做。node

须要修改源码的项目需求

须要修改源码的项目需求总共有两处:git

  1. ElementUI提供的树型组件的CheckBox须要进行置灰;
  2. ElementUI的的穿梭框中须要进行6000条以上的数据操做;

准备工做

利用 npm install element-ui --save 下载的内容中包括了ElementUI每一个组件的源码 node_modules\element-ui\packages, 以及官方当前版本打包之后的代码 node_modules\element-ui\lib, 咱们通常全量引入或者是部分引入组件都引用是打包之后的 lib 里边的代码。github

//main.js 全量引入ElementUI组件

import Vue from 'vue'
import App from './App'
import router from './router'
import store from './vuex/index'
import './libs/auto'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-default/index.css'
import './assets/common/css/reset.css'

Vue.config.productionTip = false
Vue.use(ElementUI)

/* eslint-disable no-new */
new Vue({
  el: '#app',
  store,
  router,
  ElementUI,
  template: '<App/>',
  components: { App }
})
//main.js 中部分引入组件

import Vue from 'vue'
import { Button, Select } from 'element-ui'
import App from './App.vue'

Vue.component(Button.name, Button)
Vue.component(Select.name, Select)
/* 或写为
 * Vue.use(Button)
 * Vue.use(Select)
 */

new Vue({
  el: '#app',
  render: h => h(App)
})

因此,若是利用以上两种方式是不能引用到packages里边咱们修改之后的代码的。 要用直接在packages文件里边引入模块的方式进行引入。vuex

import Button from 'element-ui/packages/button'

不过,这种方式也存在相应的问题: 因为ElementUI中使用了一些高级语法,其中包括jsx等的方式,因此须要下载相应的模块,好比‘transform-vue-jsx’。 而且在.babelrc文件中添加:chrome

"plugins": ["transform-runtime","transform-vue-jsx"],

这样,就能够跑出来了,一切看起来彷佛都好了。npm

可是,不幸的是只是添加这些babel方法并不能解决原版可以向低版本浏览器支持的能力,好比IE11就悲剧了。element-ui

因此,只能采起其余方式,即在GitHub上找到ElementUI——Dev源码,而后修改打包后直接替换lib文件里边的全部内容。segmentfault

具体如何进行ElementUI的开发,官方有相应的介绍:https://github.com/ElemeFE/element/blob/master/.github/CONTRIBUTING.zh-CN.md

问题分析与解决方法

  1. 针对第一个问题,因为原生的树型组件(Tree)并无提供对于Checkbox进行置灰的操做,可是经过观察Tree这个组件的源码,
//tree-node.vue文件

<template>
// ...
<el-checkbox
    v-if="showCheckbox"
    v-model="node.checked"
    :indeterminate="node.indeterminate"
    @change="handleCheckChange"
    @click.native.stop="handleUserClick">
</el-checkbox>
//...
</template>

<script type="text/jsx">
    //...
    import ElCheckbox from 'element-ui/packages/checkbox';
    //...
</script>

咱们很明显可以看出,其中使用的CheckBox是经过引入ElCheckbox这个组件来生成的,因此ElCheckBox这个组件时有disabled这个属性的,因此给饮用的CheckBox这个组件添加disabled为true便可。

//tree-node.vue文件

<el-checkbox
    v-if="showCheckbox"
    v-model="node.checked"
    :indeterminate="node.indeterminate"
    :disabled="checkboxDisabled"
    @change="handleCheckChange"
    @click.native.stop="handleUserClick">
</el-checkbox>

而且能够经过props一层一层把设置的变量传递到tree-node.vue文件。而且tree组件的特色,tree-node里边还有下一层的tree-node,因此也要把checkboxDisabled这个属性进行递归传递。

//tree-node.vue
<template>
    //...
    <el-checkbox
        v-if="showCheckbox"
        v-model="node.checked"
        :indeterminate="node.indeterminate"
        :disabled="checkboxDisabled"
        @change="handleCheckChange"
        @click.native.stop="handleUserClick">
      </el-checkbox>
      <span
        v-if="node.loading"
        class="el-tree-node__loading-icon el-icon-loading">
      </span>
      <node-content :node="node"></node-content>
    </div>
    <el-collapse-transition>
      <div
        class="el-tree-node__children"
        v-show="expanded">
        <el-tree-node
          :checkboxDisabled="checkboxDisabled"
          :render-content="renderContent"
          v-for="child in node.childNodes"
          :key="getNodeKey(child)"
          :node="child"
          @node-expand="handleChildNodeExpand">
        </el-tree-node>
      </div>
    </el-collapse-transition>
    //...
</template>

<script type="text/jsx">
  import ElCollapseTransition from 'element-ui/src/transitions/collapse-transition';
  import ElCheckbox from 'element-ui/packages/checkbox';
  import emitter from 'element-ui/src/mixins/emitter';

  export default {
    name: 'ElTreeNode',

    componentName: 'ElTreeNode',

    mixins: [emitter],

    props: {
      node: {
        default() {
          return {};
        }
      },
      checkboxDisabled: Boolean,
      props: {},
      renderContent: Function
    }
//...  
</script>

同时,tree.vue这个文件也要进行参数传递:

//tree.vue文件
<template>
  <div class="el-tree" :class="{ 'el-tree--highlight-current': highlightCurrent }">
    <el-tree-node
      v-for="child in root.childNodes"
      :checkboxDisabled="checkboxDisabled"
      :node="child"
      :props="props"
      :key="getNodeKey(child)"
      :render-content="renderContent"
      @node-expand="handleNodeExpand">
    </el-tree-node>
    <div class="el-tree__empty-block" v-if="!root.childNodes || root.childNodes.length === 0">
      <span class="el-tree__empty-text">{{ emptyText }}</span>
    </div>
  </div>
</template>

<script>
//...
    props: {
      data: {
        type: Array
      },
      checkboxDisabled: Boolean,
      //...
    },
//...
</script>

固然,在使用tree这个组件的地方也相应的把属性传下去。

//项目文件.vue
<el-tree
    :data="allMenuPermissions"
    :checkboxDisabled=true
    default-expand-all
    show-checkbox
    node-key="funsflowId"
    ref="tree"
    highlight-current
    :props="defaultProps">
</el-tree>

到如今为止,第一个问题解决了,经过打包验证可以实现对tree组件中的CheckBox进行置灰的操做了。

  1. 对于第二个问题,是因为数据量过大,若是将所有数据渲染到页面上,会致使节点数量过多,页面卡顿。

因此解决的办法就是要减小页面节点渲染,而且可以在进行搜索的时候可以对全量数据进行搜索。

找到动态生成每一项的穿梭框文件transfer-panel组件,

//transfer-panel.vue文件
<template>
    //...
    <el-checkbox-group
        v-model="checked"
        v-show="!hasNoMatch && data.length > 0"
        :class="{ 'is-filterable': filterable }"
        class="el-transfer-panel__list"
        :isShow="isShowItem(item,filteredData)">
        <el-checkbox
          class="el-transfer-panel__item"
          v-for="item in filteredData"
          :label="item[keyProp]"
          :disabled="item[disabledProp]"
          :key="item[keyProp]">
          <option-content :option="item"></option-content>
    </el-checkbox>
    //...
</template>

首先,里边的<el-checkbox></el-checkbox>中的内容是要根据显示的数据个数进行动态循环生成。

要达到减小节点的目的,刚开始的思路是利用vue提供的v-if指令,当须要显示的数据长度超过100条是,

就将让v-if=false,达到不渲染当前节点的目的。

修改代码:

//transfer-panel.vue文件
<template>
    //...
    <el-checkbox-group
        v-model="checked"
        v-show="!hasNoMatch && data.length > 0"
        :class="{ 'is-filterable': filterable }"
        class="el-transfer-panel__list"
        :isShow="isShowItem(item,filteredData)">
        <el-checkbox
          class="el-transfer-panel__item"
          v-for="item in filteredData"
          v-if="showItem(item)"
          :label="item[keyProp]"
          :disabled="item[disabledProp]"
          :key="item[keyProp]">
          <option-content :option="item"></option-content>
    </el-checkbox>
    //...
</template>

<script>
    //...
    methods: {
        //...
        showItem(i){
            if(filteredData.indexOf(i) > 100){
                return false;
            } else {
                return true;
            }
        }
        //...
    }
    //...
</script>

这样就能够根据数据渲染的数据在总体数据中的位置来判断,是否要渲染这条数据对应的节点。

这样的处理方式在高版本的处理器(chrome58以上)上是没有问题的,可是在低一点的版本的浏览器或者性能低一点的FireFox或者IE上仍是会出现卡顿的现象。

经过查看节点,相对应的节点也是已经没有显示渲染了。可是没有渲染的部分莫名其妙的出现了不少的<!---->这样的注释内容。经过把HTML文件拷贝出来查看,发现文件总共大小有12M以上,因为动手吧<!---->以及换行和空格删除后,发现整个文件大小缩小为2M左右。

因此,基本能够确定卡顿是因为渲染出来的HTML文件内容过多引发的。

大概估计这个因为Vue中的<template>标签引发的,虽然具体的内容节点没有渲染,

可是CheckBox这个组件的循环渲染也会出现不少的空的<template></template>,这个空标签就会渲染成<!--->;

经过在segmentfault上向询问,你们给出的建议是经过修改渲染的数据,只循环渲染前100条数据以达到目的。最后的实现方式也是这样作的。

采用这种方式就是要当心搜索的数据和最终渲染的数据要进行关联和解关联。

//transfer-panel.vue文件
<script>
//...
    computed: {
      filteredData() {
        let arrData = this.data.filter(item => {
          if (typeof this.filterMethod === 'function') {
            return this.filterMethod(this.query, item);
          } else {
            const label = item[this.labelProp] || item[this.keyProp].toString();
            return label.toLowerCase().indexOf(this.query.toLowerCase()) > -1;
          }
        });
        if (arrData.length > 100) {
          this.showFilteredData = arrData.slice(0, 100);
        } else {
          this.showFilteredData = arrData;
        }
        return arrData;
      },
    }

//...
</script>

在渲染的数据生成的时候,就对this.showFilteredData这个要渲染的数据进行赋值。

若是总数据超过100条,那就截取前100条赋值为this.showFilteredData。

这样就可以保证最多有100条数据进行循环渲染。

最终解决

这样修改完代码,而后执行npm run dist,就能够再次生成lib文件中的代码。替换到相对应的文件element-ui中的lib中,就能够达到解决问题的目的。

 

相关文章
相关标签/搜索