加快Vue項目的開發速度

現現在的開發,好比是內部使用的管理平臺這種項目大都時間比較倉倉促。實際上來講在使用了webpack + vue 這一套來開發的話已經大大了提升了效率。可是對於咱們的開發層面。仍是有不少地方能夠再次提升咱們的項目開發效率,讓咱們更加專一於業務,畢竟時間就是生命。下面咱們挨個來探討。

巧用Webpack

Webpack是實現咱們前端項目工程化的基礎,但其實她的用處遠不只僅如此,咱們能夠經過Webpack來幫咱們作一些自動化的事情。首先咱們要了解require.context()這個APIjavascript

require.context()

您可使用 require.context()函數建立本身的上下文。 它容許您傳入一個目錄進行搜索,一個標誌指示是否應該搜索子目錄,還有一個正則表達式來匹配文件。

實際上是Webpack經過解析 require() 的調用,提取出來以下這些信息:html

Directory: ./template
Regular expression: /^.*\.ejs$/

而後來建立咱們本身的上下文,什麼意思呢,就是咱們能夠經過這個方法篩選出來咱們須要的文件而且讀取前端

下面咱們來簡單看一看使用:vue

/**
* @param directory 要搜索的文件夾目錄不能是變量,不然在編譯階段沒法定位目錄
* @param useSubdirectories  是否搜索子目錄
* @param regExp 匹配文件的正則表達式
* @return function 返回一個具備 resolve, keys, id 三個屬性的方法
          resolve() 它返回請求被解析後獲得的模塊 id
          keys() 它返回一個數組,由全部符合上下文模塊處理的請求組成。 
          id 是上下文模塊裏面所包含的模塊 id. 它可能在你使用 module.hot.accept 的時候被用到
*/
require.context('demo', useSubdirectories = false, regExp = /\.js$/)
// (建立了)一個包含了 demo 文件夾(不包含子目錄)下面的、全部文件名以 `js` 結尾的、能被 require 請求到的文件的上下文。

不要困惑,接下來咱們來探討在項目中怎麼用。java

組織路由

對於Vue中的路由,你們都很熟悉,相似於聲明式的配置文件,其實已經很簡潔了。如今咱們來讓他更簡潔node

  1. 分割路由webpack

    首先爲了方便咱們管理,咱們把router目錄下的文件分割爲如下結構git

    router                           // 路由文件夾
      |__index.js                    // 路由組織器:用來初始化路由等等
      |__common.js                   // 通用路由:聲明通用路由
      |__modules                     // 業務邏輯模塊:因此的業務邏輯模塊
            |__index.js              // 自動化處理文件:自動引入路由的核心文件
            |__home.js               // 業務模塊home:業務模塊
            |__a.js                  // 業務模塊a
  2. modules文件夾中處理業務模塊github

    modules文件夾中存放着咱們全部的業務邏輯模塊,至於業務邏輯模塊怎麼分,我相信你們天然有本身的一套標準。咱們經過上面提到的require.context()接下來編寫自動化的核心部分index.jsweb

    const files = require.context('.', true, /\.js$/)
    
    console.log(files.keys()) // ["./home.js"] 返回一個數組
    let configRouters = []
    files.keys().forEach(key => {
      if (key === './index.js') return
      configRouters = configRouters.concat(files(key).default) // 讀取出文件中的default模塊
    })
    export default configRouters // 拋出一個Vue-router期待的結構的數組

    自動化部分寫完了,那業務組件部分怎麼寫? 這就更簡單了

    import Frame from '@/views/frame/Frame'
    import Home from '@/views/index/index'
    export default [
        // 首頁
        {
          path: '/index',
          name: '首頁',
          redirect: '/index',
          component: Frame, 
          children: [ // 嵌套路由
            {
              path: '',
              component: Home
            }
          ]
        }
    ]
  3. common路由處理
    咱們的項目中有一大堆的公共路由須要處理好比404阿,503阿等等路由咱們都在common.js中進行處理。

    export default [
      // 默認頁面
      {
        path: '/',
        redirect: '/index',
        hidden:true
      },
      // 無權限頁面
      {
        path: '/nopermission',
        name: 'nopermission',
        component: () => import('@/views/NoPermission')
      },
      // 404
      {
        path: '*',
        name: 'lost',
        component: () => import('@/views/404')
      }
    ]
  4. 路由初始化
    這是咱們的最後一步了,用來初始化咱們的項目路由

    import Vue from 'vue'
    import VueRouter from 'vue-router'
    import RouterConfig from './modules' // 引入業務邏輯模塊
    import CommonRouters from './common' // 引入通用模塊
    Vue.use(VueRouter)
    export default new VueRouter({
      mode: 'history',// 須要服務端支持
      scrollBehavior: () => ({ y: 0 }),
      routes: RouterConfig.concat(CommonRouters)
    })

    估計有些朋友代碼寫到這還不知道到底這樣作好處在哪裏。咱們來描述一個場景,好比按照這種結構來劃分模塊。正常的狀況是咱們建立完home.js要手動的把這個模塊import到路由文件聲明的地方去使用。可是有了上面的index.js,在使用的時候你只須要去建立一個home.js並拋出一個符合VueRouter規範的數組,剩下的就不用管了。import RouterConfig from './modules' // 引入業務邏輯模塊 已經幫你處理完了。另外擴展的話你還能夠把hooks拿出來做爲一個單獨文件。

全局組件統一聲明

一樣的道理,有了上面的經驗,咱們照葫蘆畫瓢來處理一下咱們的全局組件。這就沒什麼可說的了,直接上核心代碼

  1. 組織結構

    components                       // 組件文件夾
      |__xxx.vue                     // 其餘組件
      |__global                      // 全局組件文件夾
            |__index.js              // 自動化處理文件
            |__demo.vue              // 全局demo組件
  2. global處理

    import Vue from 'vue'
    let contexts = require.context('.', false, /\.vue$/)
    contexts.keys().forEach(component => {
      let componentEntity = contexts(component).default
      // 使用內置的組件名稱 進行全局組件註冊
      Vue.component(componentEntity.name, componentEntity)
    })
  3. 使用和說明

    這個使用起來就更簡單了,直接在app.js引用這個文件就行。

    注意:我以前看到有些人作法是使用組件名去區分全局組件和普通組件,而後經過正則去判斷需不須要全局註冊。我是直接把全局的組件放到global文件夾下,而後組件的註冊名稱直接使用component.name。至於使用哪一種方式就比較看我的了。

充分利用NodeJS

放着node這麼好得東西不用真是有點浪費,那麼咱們來看看node能爲咱們增長效率作出什麼貢獻。

有這麼一個場景,咱們每次建立模塊的時候都要新建一個vue文件和對應的router配置,並且新頁面的大部分東西都還差很少,還得去複製粘貼別得頁面。這想一想就有點low。那既然有了node咱們可不能夠經過node來作這寫亂七八糟得事情? 下面來把咱們的想法付諸於顯示。

咱們實現這個功能主要要藉助Nodefsprocess, 感興趣的話能夠深刻研究一下。

首先咱們要編寫咱們的node腳本,這裏是一個比較簡單的版本。什麼驗證文件夾或者文件的都沒有,只是來實現咱們這個想法:

/*
     * fast add new module script
     */
    const path = require('path')
    const fs = require('fs')
    const chalk = require('chalk')
    const reslove = file => path.resolve(__dirname, '../src', file)
    // symbol const
    const RouterSymbol = Symbol('router'),
          ViewsSymbol = Symbol('views')
    // root path
    const rootPath = {
      [RouterSymbol]: reslove('router/modules'),
      [ViewsSymbol]: reslove('views')
    }
    //loggs
    const errorLog = error => console.log(chalk.red(`${error}`))
    const defaultLog = log => console.log(chalk.green(`${log}`))
    // module name
    let moduleName = new String()
    let fileType = new String()
    //const string
    const vueFile = module => (`<template>
    
    </template>
    
    <script>
    export default {
      name: '${module}',
      data () {
        return {
    
        }
      },
      methods: {
    
      },
      created() {
        
      }
    }
    </script>
    
    <style lang="less">
    
    </style>
    `)
    // route file
    const routerFile = module => (`// write your comment here...
    export default [
      {
        path: '/${module}',
        name: '',
        redirect: '/${module}',
        component: () => import('@/views/frame/Frame'),
        children: [
          {
            path: '',
            fullPath: '',
            name: '',
            component: () => import('@/views/${module}/index')
          }
        ]
      }
    ]
    `)
    /**
     * generate file
     * @param {*} filePath 
     * @param {*} content 
     * @param {*} dirPath 
     */
    const generateFile = async (filePath, content, dirPath = '') =>{
      try {
        // create file if file not exit
        if (dirPath !== '' && ! await fs.existsSync(dirPath)) {
          await fs.mkdirSync(dirPath)
          defaultLog(`created ${dirPath}`)
        }
        if (! await fs.existsSync(filePath)) {
          // create file
          await fs.openSync(filePath, 'w')
          defaultLog(`created ${filePath}`)
        }
        await fs.writeFileSync(filePath, content, 'utf8')
      } catch (error) {
        errorLog(error)
      }
    }
    // module-method map
    const generates = new Map([
      ['view', async (module) => {
        // module file
        const filePath = path.join(rootPath[ViewsSymbol], module)
        const vuePath = path.join(filePath, '/index.vue')
        await generateFile(vuePath, vueFile(module), filePath)
      }],
      // router is not need new folder
      ['router',async (module) => {
        const routerPath = path.join(rootPath[RouterSymbol], `/${module}.js`)
        await generateFile(routerPath, routerFile(module))
      }]
    ])
    defaultLog(`請輸入模塊名稱(英文):`)
    // files
    const files = ['view', 'router']
    // 和命令行進行交互 獲取的建立的模塊名稱
    process.stdin.on('data', (chunk) => {
      try {
        if (!moduleName) {
          moduleName = chunk
        } else {
          chunk = chunk.slice(0,-2) // delete /n
          defaultLog(`new module name is ${chunk}`)
          files.forEach(async (el, index) => {
            // 執行建立語句
            await generates.get(`${el}`).call(null, chunk.toString())
            if (index === files.length-1) {
              process.stdin.emit('end')
            }
          })
        }
      } catch (error) {
        errorLog(error)
      }
    })
    process.stdin.on('end', () => {
      defaultLog('create module success')
    })

下面咱們看使用的流程

圖片描述
這樣咱們就分別建立了vuerouter的文件,並且已經注入了內容。按照咱們提早聲明的組件

注意:這只是一個簡單的思路,經過Node強大的文件處理能力,咱們能作的事情遠不止這些。

發揮Mixins的威力

Vue中的混入mixins是一種提供分發 Vue 組件中可複用功能的很是靈活的方式。據說在3.0版本中可能會用Hooks的形式實現,但這並不妨礙它的強大。基礎部分的能夠看這裏。這裏主要來討論mixins能在什麼情景下幫助咱們。

通用mixins

若是咱們有大量的表格頁面,仔細一扒拉你發現很是多的東西都是能夠複用的例如分頁表格高度加載方法laoding聲明等一大堆的東西。下面咱們來整理出來一個簡單通用混入list.js

const list = {
  data () {
    return {
      // 這些東西咱們在list中處理,就不須要在每一個頁面再去手動的作這個了。
      loading: false, // 伴隨loading狀態
      pageNo: 1, // 頁碼
      pageSize: 15, // 頁長
      totalCount: 0, // 總個數
      pageSizes: [15, 20, 25, 30], //頁長數
      pageLayout: 'total, sizes, prev, pager, next, jumper', // 分頁佈局
      list: []
    }
  },
  methods: {
    // 分頁回掉事件
    handleSizeChange(val) {
      this.pageSize = val
      // todo
    },
    handleCurrentChange (val) {
      this.pageNo = val
      // todo
    },
    /**
     * 表格數據請求成功的回調 處理完公共的部分(分頁,loading取消)以後把控制權交給頁面
     * @param {*} apiResult 
     * @returns {*} promise
     */
    listSuccessCb (apiResult = {}) {
      return new Promise((reslove, reject) => {
        let tempList = [] // 臨時list
        try {
          this.loading = false
          // todo
          // 直接拋出
          reslove(tempList)
        } catch (error) {
          reject(error)
        }
      })
    },
    /**
     * 處理異常狀況
     * ==> 簡單處理  僅僅是對錶格處理爲空以及取消loading
     */
    listExceptionCb (error) {
      this.loading = false
      console.error(error)
    }
  },
  created() {
    // 這個生命週期是在使用組件的生命週期以前
    this.$nextTick().then(() => {
      // todo
    })
  }
}
export default list

下面咱們直接在組件中使用這個mixins

import mixin from '@/mixins/list' // 引入
import {getList} from '@/api/demo'
export default {
  name: 'mixins-demo',
  mixins: [mixin], // 使用mixins
  data () {
    return {
    }
  },
  methods: {
    // 加載列表
    load () {
      const para = {
      }
      this.loading = true
      getList(para).then((result) => {
        this.listSuccessCb(result).then((list) => {
          this.list = list
        }).catch((err) => {
          console.log(err)
        })
      }).catch((err) => {
        this.listExceptionCb(err)
      })
    }
  },
  created() {
    this.load()
  }
}

使用了mixins以後一個簡單的有loadoing, 分頁,數據的表格大概就只須要上面這些代碼。

mixins作公共數據的管理

有些時候咱們有一些公共的數據它可能3,4個模塊取使用可是又達不到全局的這種規模。這個時候咱們就能夠用mixins去管理他們,好比咱們有幾個模塊要使用用戶類型這個列表,咱們來看使用mixins來實現共享。

// types.js
import {getTypes} from '@/api/demo' // ajax
export default {
  data () {
    return {
      types: [] // ==>  {name: '', value: ''}
    }
  },
  methods: {
    // 獲取列表
    getAllTypesList () {
      getApiList().then((result) => {
        // todo
        this.types = result // 假設result就是咱們須要使用的數據
      }).catch((err) => {
        console.error(err)
      })
    }
  },
  created() {
    // 在須要使用這個mixins的時候取自動請求數據  這個可要可不要  你想在父組件中執行也是ok的
    this.getAllTypesList()
  }
}

在組件中引用

import typeMixin from '@/mixins/types'
export default {
  name: 'template',
  mixins: [typeMixin],
  data () {
    return {
      // types這個數組在使用組件中不用多餘的定義,直接拿來用就行
      type: ''
    }
  },
  methods: {
  }
}

至於mixins中得數據咱們能夠在組件中直接使用

<!--  -->
<el-select v-model="type" clearable placeholder="請選擇類型">
    <el-option v-for="item in types" :key="item.id" :label="item.templateName" :value="item.id"></el-option>
  </el-select>

咱們這樣就能夠不用vuex來去管理那些只有在模塊間複用幾回的數據,並且很是方便得去取咱們想要得數據,連定義都省了。可是這有一個缺點。就是每次都會去從新請求這些數據。若是你不在意這一點點瑕疵的話,我以爲用起來是徹底ok得。

注意: <font color="red">mixins它當然是簡單的,可是註釋和引用必定要作好,否則的話新成員進入團隊大概是一臉的懵逼,並且也不利於後期的維護。也是一把雙刃劍。另外:全局mixins必定要慎用,若是不是必需要用的話我仍是不建議使用。</font>

進一步對組件進行封裝

你們都知道組件化的最大的好處就是高度的可複用性和靈活性。可是組件怎麼封裝好,封裝到什麼程度讓咱們更方便。這是沒有標準的答案的。咱們只有根據高內聚,低耦合的這個指導思想來對咱們的業務通用組件來進行封裝,讓咱們的業務頁面結構更加的簡潔,加快咱們的開發效率。封裝多一點的話頁面可能會變成這樣:

<template>
  <box-content>
    <!-- 頭部標題部分 -->
    <page-title>
      <bread slot="title" :crumbs="[{name: 'xx管理', path: '', active: true, icon: ''}, {name: 'xxxx', path: '', active: true, icon: ''}]"></bread>
    </page-title>
    <!-- 表格部分 -->
    <div>
      <base-table v-loading="loading" :columns="headers" :list="list" :page-no ="pageNo" :page-size="pageSize" :total-count="totalCount" @delete="deleteItm"  @change-size="handleSizeChange" @change-page="handleCurrentChange">
      </base-table>
    </div>
  </box-content>
</template>

有什麼東西一目瞭然。

無狀態組件

最容易勾起咱們封裝慾望的就是無狀態HTML組件,例如咱們除去header, menu以後的content部分。沒有什麼須要複雜的交互,可是咱們每一個頁面又都得寫。你說不拿它開刀拿誰開🔪

<template>
  <div class="container-fluid" :class="[contentClass]">
      <el-row>
          <el-col :span="24">
              <!-- box with #fff bg -->
              <div class="box">
                  <div class="box-body">
                      <slot></slot>
                  </div>
              </div>
          </el-col>
      </el-row>
  </div>
</template>

上面這個處理很是的簡單,可是你在項目中會很是頻繁的使用過到,那麼這個封裝就頗有必要了。

ElementUI table組件封裝

ElementUI中得組件其實已經封裝得很優秀了,可是表格使用得時候仍是有一堆得代碼在我看來是不須要在業務中重複寫得。封裝到靠配置來進行表格得書寫得一步我以爲就差很少了,下面是一個小demo

<template>
  <el-row>
    <el-col :span="24">
      <el-table :data="list" border size="mini" @selection-change="handleSelectionChange" :max-height="tableHeight" v-bind="$attrs"> <!--   -->
        <template v-for="(column, index) in columns">
          <slot name="front-slot"> </slot>
          <!-- 序號 -->
          <el-table-column :key="index" v-if="column.type === 'selection'" type="selection" width="55"> </el-table-column>
          <!-- 複選框 -->
          <el-table-column :key="index" v-else-if="column.type === 'index'"  type="index" width="50" label="序號"> </el-table-column>
          <!-- 具體內容 -->
          <el-table-column :key="index" v-else align="left" :label="column.title" :width="column.width">
            <template slot-scope="scope">
              <!-- 僅僅顯示文字 -->
              <label v-if="!column.hidden"> <!-- 若是hidden爲true的時候 那麼當前格能夠不顯示,能夠選擇顯示自定義的slot-->
                <!-- 操做按鈕 -->
                <label v-if="column.type === 'operate'">
                  <a href="javascript:void(0)" class="operate-button" v-for="(operate, index) in column.operates" :key="index" @click="handleClick(operate, scope.row)">
                    {{operate.name}}
                    &nbsp;&nbsp;
                  </a>
                </label>
                <span v-else>
                  {{scope.row[column.key]}}
                </span>
              </label>
              <!-- 使用slot的狀況下 -->
              <label v-if="column.slot">
                <!-- 具名slot -->
                <slot v-if="column.slot" :name="column.slot" :scope="scope"></slot>
              </label>
            </template>
          </el-table-column>
        </template>
        <!--默認的slot -->
        <slot/>
      </el-table>
    </el-col>
  </el-row>
</template>
export default {
  name: 'base-table',
  props: {
    // 核心數據
    list: {
      type: Array,
      default: () => []
    },
    // columns
    columns: {
      type: Array,
      required: true,
      default: () => []
    }
  },
  data () {
    return {
      tableHeight: xxx
    }
  },
  methods: {
    // 處理點擊事件
    handleClick(action, data) {
      // emit事件
      this.$emit(`${action.emitKey}`, data)
    }
  }
}

使用:

<base-table v-loading="loading" :columns="headers" :list="list"  @view="viewCb">
  <!-- 自定義的slot -->
  <template slot="demoslot" slot-scope="{scope}">
    <span>
      {{scope.row}}
    </span>
  </template>
  <!-- 默認的slot  若是交互很複雜 咱們還能夠直接使用表格內部的組件 -->
  <el-table-column
    label="操做"
    width="200"
  >
    <template slot-scope="scope">
      <a href="javascript:void(0)" @click="defaultSlot(scope.row)">xxx</a>
    </template>
  </el-table-column>
</base-table>
export default {
  name: 'table-demo',
  data () {
    return {
      // 表格頭部配置
      headers: [
        { key: 'xxx', title: '測試' },
        { title: 'xxx', hidden: true, slot: 'demoslot'},
        {
          title: '操做', type: 'operate',
          operates: [
            {name: '詳情',emitKey: 'view'}
          ]
        }
      ]
    }
  },
  methods: {
    viewCb(){
      // todo
    },
    defaultSlot(){
      // todo
    }
  }
}

這樣封裝過的表格,應付基本的一些需求問題應該不大。至於特殊的要求能夠一步一步的進行完善。

總結

這些東西並非什麼語法糖,是真正能夠在項目中加快咱們的效率。讓咱們的本身乃至整個團隊從繁雜的重複複製粘貼中解脫一點。至於速度和質量的問題。我是以爲使用公共組件質量可控性會更高一些。我建議公共得東西註釋必定要寫得全面和詳細,這樣能夠極大的下降咱們的交流成本。至於組件的封裝仍是要看你的業務。

以上觀點純屬我的意見,若有錯誤,多謝指正。

沒想到這麼多人看😂,加班加點整理出來了示例代碼,比較簡陋😂😂

原文地址 若是以爲有用得話給個⭐吧

相關文章
相關標籤/搜索