修改ElementUI源碼實踐


提要

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

Vue2.0+Vuex+ElementUI是現在很多項目都在使用的BS軟件的開發組合。

Vue相較於Angular具有學習成本低,上手快以及組件輕量化的特點;相較於React,其官方提供的很多指令以及可以自定義的指令能夠為讓開發更加高效。並且相較於React生命周期監聽所有props和state的變化,Vue中提供的watch方法監聽單個數據的變化,能夠更加直觀的進行數據操作。

需要修改源碼的項目需求

需要修改源碼的項目需求總共有兩處:

  1. ElementUI提供的樹型組件的CheckBox需要進行置灰;
  2. ElementUI的的穿梭框中需要進行6000條以上的數據操作;

准備工作

利用 npm install element-ui --save 下載的內容中包括了ElementUI每個組件的源碼 node_modules\element-ui\packages, 以及官方當前版本打包以后的代碼 node_modules\element-ui\lib, 我們一般全量引入或者是部分引入組件都引用是打包以后的 lib 里邊的代碼。

//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文件里邊引入模塊的方式進行引入。

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

不過,這種方式也存在相應的問題: 由於ElementUI中使用了一些高級語法,其中包括jsx等的方式,所以需要下載相應的模塊,比如‘transform-vue-jsx’。 並且在.babelrc文件中添加:

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

這樣,就可以跑出來了,一切看起來似乎都好了。

但是,不幸的是只是添加這些babel方法並不能解決原版能夠向低版本瀏覽器支持的能力,比如IE11就悲劇了。

所以,只能采取其他方式,即在GitHub上找到ElementUI——Dev源碼,然后修改打包后直接替換lib文件里邊的所有內容。

具體如何進行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中,就可以達到解決問題的目的。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM