加快Vue項(xiàng)目的開(kāi)發(fā)速度的方法
現(xiàn)如今的開(kāi)發(fā),比如是內(nèi)部使用的管理平臺(tái)這種項(xiàng)目大都時(shí)間比較倉(cāng)倉(cāng)促。實(shí)際上來(lái)說(shuō)在使用了webpack + vue 這一套來(lái)開(kāi)發(fā)的話已經(jīng)大大了提高了效率。但是對(duì)于我們的開(kāi)發(fā)層面。還是有很多地方可以再次提高我們的項(xiàng)目開(kāi)發(fā)效率,讓我們更加專(zhuān)注于業(yè)務(wù),畢竟時(shí)間就是生命。下面我們挨個(gè)來(lái)探討。
巧用Webpack
Webpack
是實(shí)現(xiàn)我們前端項(xiàng)目工程化的基礎(chǔ),但其實(shí)她的用處遠(yuǎn)不僅僅如此,我們可以通過(guò)Webpack
來(lái)幫我們做一些自動(dòng)化的事情。首先我們要了解require.context()
這個(gè)API
require.context()
您可以使用require.context()函數(shù)創(chuàng)建自己的上下文。 它允許您傳入一個(gè)目錄進(jìn)行搜索,一個(gè)標(biāo)志指示是否應(yīng)該搜索子目錄,還有一個(gè)正則表達(dá)式來(lái)匹配文件。
其實(shí)是Webpack
通過(guò)解析 require()
的調(diào)用,提取出來(lái)如下這些信息:
Directory: ./template Regular expression: /^.*\.ejs$/
然后來(lái)創(chuàng)建我們自己的上下文,什么意思呢,就是我們可以通過(guò)這個(gè)方法篩選出來(lái)我們需要的文件并且讀取
下面我們來(lái)簡(jiǎn)單看一看使用:
/** * @param directory 要搜索的文件夾目錄不能是變量,否則在編譯階段無(wú)法定位目錄 * @param useSubdirectories 是否搜索子目錄 * @param regExp 匹配文件的正則表達(dá)式 * @return function 返回一個(gè)具有 resolve, keys, id 三個(gè)屬性的方法 resolve() 它返回請(qǐng)求被解析后得到的模塊 id keys() 它返回一個(gè)數(shù)組,由所有符合上下文模塊處理的請(qǐng)求組成。 id 是上下文模塊里面所包含的模塊 id. 它可能在你使用 module.hot.accept 的時(shí)候被用到 */ require.context('demo', useSubdirectories = false, regExp = /\.js$/) // (創(chuàng)建了)一個(gè)包含了 demo 文件夾(不包含子目錄)下面的、所有文件名以 `js` 結(jié)尾的、能被 require 請(qǐng)求到的文件的上下文。
不要困惑,接下來(lái)我們來(lái)探討在項(xiàng)目中怎么用。
組織路由
對(duì)于Vue
中的路由,大家都很熟悉,類(lèi)似于聲明式的配置文件,其實(shí)已經(jīng)很簡(jiǎn)潔了。現(xiàn)在我們來(lái)讓他更簡(jiǎn)潔
分割路由
首先為了方便我們管理,我們把router
目錄下的文件分割為以下結(jié)構(gòu)
router // 路由文件夾 |__index.js // 路由組織器:用來(lái)初始化路由等等 |__common.js // 通用路由:聲明通用路由 |__modules // 業(yè)務(wù)邏輯模塊:所以的業(yè)務(wù)邏輯模塊 |__index.js // 自動(dòng)化處理文件:自動(dòng)引入路由的核心文件 |__home.js // 業(yè)務(wù)模塊home:業(yè)務(wù)模塊 |__a.js // 業(yè)務(wù)模塊a
modules文件夾中處理業(yè)務(wù)模塊
modules
文件夾中存放著我們所有的業(yè)務(wù)邏輯模塊,至于業(yè)務(wù)邏輯模塊怎么分,我相信大家自然有自己的一套標(biāo)準(zhǔn)。我們通過(guò)上面提到的require.context()
接下來(lái)編寫(xiě)自動(dòng)化的核心部分index.js
。
const files = require.context('.', true, /\.js$/) console.log(files.keys()) // ["./home.js"] 返回一個(gè)數(shù)組 let configRouters = [] files.keys().forEach(key => { if (key === './index.js') return configRouters = configRouters.concat(files(key).default) // 讀取出文件中的default模塊 }) export default configRouters // 拋出一個(gè)Vue-router期待的結(jié)構(gòu)的數(shù)組
自動(dòng)化部分寫(xiě)完了,那業(yè)務(wù)組件部分怎么寫(xiě)? 這就更簡(jiǎn)單了
import Frame from '@/views/frame/Frame' import Home from '@/views/index/index' export default [ // 首頁(yè) { path: '/index', name: '首頁(yè)', redirect: '/index', component: Frame, children: [ // 嵌套路由 { path: '', component: Home } ] } ]
common路由處理
我們的項(xiàng)目中有一大堆的公共路由需要處理比如404
阿,503
阿等等路由我們都在common.js
中進(jìn)行處理。
export default [ // 默認(rèn)頁(yè)面 { path: '/', redirect: '/index', hidden:true }, // 無(wú)權(quán)限頁(yè)面 { path: '/nopermission', name: 'nopermission', component: () => import('@/views/NoPermission') }, // 404 { path: '*', name: 'lost', component: () => import('@/views/404') } ]
路由初始化
這是我們的最后一步了,用來(lái)初始化我們的項(xiàng)目路由
import Vue from 'vue' import VueRouter from 'vue-router' import RouterConfig from './modules' // 引入業(yè)務(wù)邏輯模塊 import CommonRouters from './common' // 引入通用模塊 Vue.use(VueRouter) export default new VueRouter({ mode: 'history',// 需要服務(wù)端支持 scrollBehavior: () => ({ y: 0 }), routes: RouterConfig.concat(CommonRouters) })
估計(jì)有些朋友代碼寫(xiě)到這還不知道到底這樣做好處在哪里。我們來(lái)描述一個(gè)場(chǎng)景,比如按照這種結(jié)構(gòu)來(lái)劃分模塊。正常的情況是我們創(chuàng)建完home.js
要手動(dòng)的把這個(gè)模塊import
到路由文件聲明的地方去使用。但是有了上面的index.js
,在使用的時(shí)候你只需要去創(chuàng)建一個(gè)home.js
并拋出一個(gè)符合VueRouter
規(guī)范的數(shù)組,剩下的就不用管了。import RouterConfig from './modules' // 引入業(yè)務(wù)邏輯模塊
已經(jīng)幫你處理完了。另外擴(kuò)展的話你還可以把hooks
拿出來(lái)作為一個(gè)單獨(dú)文件。
全局組件統(tǒng)一聲明
同樣的道理,有了上面的經(jīng)驗(yàn),我們照葫蘆畫(huà)瓢來(lái)處理一下我們的全局組件。這就沒(méi)什么可說(shuō)的了,直接上核心代碼
組織結(jié)構(gòu)
components // 組件文件夾 |__xxx.vue // 其他組件 |__global // 全局組件文件夾 |__index.js // 自動(dòng)化處理文件 |__demo.vue // 全局demo組件
global處理
import Vue from 'vue' let contexts = require.context('.', false, /\.vue$/) contexts.keys().forEach(component => { let componentEntity = contexts(component).default // 使用內(nèi)置的組件名稱(chēng) 進(jìn)行全局組件注冊(cè) Vue.component(componentEntity.name, componentEntity) })
使用和說(shuō)明
這個(gè)使用起來(lái)就更簡(jiǎn)單了,直接在app.js
引用這個(gè)文件就行。
注意:我之前看到有些人做法是使用組件名去區(qū)分全局組件和普通組件,然后通過(guò)正則去判斷需不需要全局注冊(cè)。我是直接把全局的組件放到global
文件夾下,然后組件的注冊(cè)名稱(chēng)直接使用component.name
。至于使用哪種方式就比較看個(gè)人了。
充分利用NodeJS
放著node
這么好得東西不用真是有點(diǎn)浪費(fèi),那么我們來(lái)看看node
能為我們?cè)黾有首龀鍪裁簇暙I(xiàn)。
有這么一個(gè)場(chǎng)景,我們每次創(chuàng)建模塊的時(shí)候都要新建一個(gè)vue
文件和對(duì)應(yīng)的router
配置,而且新頁(yè)面的大部分東西都還差不多,還得去復(fù)制粘貼別得頁(yè)面。這想想就有點(diǎn)low
。那既然有了node
我們可不可以通過(guò)node
來(lái)做這寫(xiě)亂七八糟得事情? 下面來(lái)把我們的想法付諸于顯示。
我們實(shí)現(xiàn)這個(gè)功能主要要借助Node
的fs和process, 感興趣的話可以深入研究一下。
首先我們要編寫(xiě)我們的node
腳本,這里是一個(gè)比較簡(jiǎn)單的版本。什么驗(yàn)證文件夾或者文件的都沒(méi)有,只是來(lái)實(shí)現(xiàn)我們這個(gè)想法:
/* * 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(`請(qǐng)輸入模塊名稱(chēng)(英文):`) // files const files = ['view', 'router'] // 和命令行進(jìn)行交互 獲取的創(chuàng)建的模塊名稱(chēng) 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) => { // 執(zhí)行創(chuàng)建語(yǔ)句 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') })
下面我們看使用的流程
這樣我們就分別創(chuàng)建了vue
和router
的文件,而且已經(jīng)注入了內(nèi)容。按照我們提前聲明的組件
注意:這只是一個(gè)簡(jiǎn)單的思路,通過(guò)Node強(qiáng)大的文件處理能力,我們能做的事情遠(yuǎn)不止這些。
發(fā)揮Mixins的威力
Vue
中的混入
mixins是一種提供分發(fā) Vue
組件中可復(fù)用功能的非常靈活的方式。聽(tīng)說(shuō)在3.0版本中可能會(huì)用Hooks的形式實(shí)現(xiàn),但這并不妨礙它的強(qiáng)大?;A(chǔ)部分的可以看這里。這里主要來(lái)討論mixins
能在什么情景下幫助我們。
比如我們的大量的表格頁(yè)面,仔細(xì)一扒拉你發(fā)現(xiàn)非常多的東西都是可以復(fù)用的例如分頁(yè)
,表格高度
,加載方法
, laoding聲明
等一大堆的東西。下面我們來(lái)整理出來(lái)一個(gè)簡(jiǎn)單的list.vue
const list = { data () { return { // 這些東西我們?cè)趌ist中處理,就不需要在每個(gè)頁(yè)面再去手動(dòng)的做這個(gè)了。 loading: false, // 伴隨loading狀態(tài) pageNo: 1, // 頁(yè)碼 pageSize: 15, // 頁(yè)長(zhǎng) totalCount: 0, // 總個(gè)數(shù) pageSizes: [15, 20, 25, 30], //頁(yè)長(zhǎng)數(shù) pageLayout: 'total, sizes, prev, pager, next, jumper', // 分頁(yè)布局 list: [] } }, methods: { // 分頁(yè)回掉事件 handleSizeChange(val) { this.pageSize = val // todo }, handleCurrentChange (val) { this.pageNo = val // todo }, /** * 表格數(shù)據(jù)請(qǐng)求成功的回調(diào) 處理完公共的部分(分頁(yè),loading取消)之后把控制權(quán)交給頁(yè)面 * @param {*} apiResult * @returns {*} promise */ listSuccessCb (apiResult = {}) { return new Promise((reslove, reject) => { let tempList = [] // 臨時(shí)list try { this.loading = false // todo // 直接拋出 reslove(tempList) } catch (error) { reject(error) } }) }, /** * 處理異常情況 * ==> 簡(jiǎn)單處理 僅僅是對(duì)表格處理為空以及取消loading */ listExceptionCb (error) { this.loading = false console.error(error) } }, created() { // 這個(gè)生命周期是在使用組件的生命周期之前 this.$nextTick().then(() => { // todo }) } } export default list
下面我們直接在組件中使用這個(gè)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() } } </script>
使用了mixins
之后一個(gè)簡(jiǎn)單的有loadoing
, 分頁(yè)
,數(shù)據(jù)
的表格大概就只需要上面這些代碼。
注意: <font color="red">mixins
它固然是簡(jiǎn)單的,但是注釋和引用一定要做好,不然的話新成員進(jìn)入團(tuán)隊(duì)大概是一臉的懵逼,而且也不利于后期的維護(hù)。也是一把雙刃劍。另外:全局mixins
一定要慎用,如果不是必須要用的話我還是不建議使用。</font>
進(jìn)一步對(duì)組件進(jìn)行封裝
大家都知道組件化的最大的好處就是高度的可復(fù)用性和靈活性。但是組件怎么封裝好,封裝到什么程度讓我們更方便。這是沒(méi)有標(biāo)準(zhǔn)的答案的。我們只有根據(jù)高內(nèi)聚,低耦合
的這個(gè)指導(dǎo)思想來(lái)對(duì)我們的業(yè)務(wù)通用組件來(lái)進(jìn)行封裝,讓我們的業(yè)務(wù)頁(yè)面結(jié)構(gòu)更加的簡(jiǎn)潔,加快我們的開(kāi)發(fā)效率。封裝多一點(diǎn)的話頁(yè)面可能會(huì)變成這樣:
<template> <box-content> <!-- 頭部標(biāo)題部分 --> <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>
有什么東西一目了然。
無(wú)狀態(tài)組件
最容易勾起我們封裝欲望的就是無(wú)狀態(tài)HTML
組件,例如我們除去header
, menu
之后的content
部分。沒(méi)有什么需要復(fù)雜的交互,但是我們每個(gè)頁(yè)面又都得寫(xiě)。你說(shuō)不拿它開(kāi)刀拿誰(shuí)開(kāi)🔪
<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>
上面這個(gè)處理非常的簡(jiǎn)單,但是你在項(xiàng)目中會(huì)非常頻繁的使用過(guò)到,那么這個(gè)封裝就很有必要了。
ElementUI table組件封裝
ElementUI
中得組件其實(shí)已經(jīng)封裝得很優(yōu)秀了,但是表格使用得時(shí)候還是有一堆得代碼在我看來(lái)是不需要在業(yè)務(wù)中重復(fù)寫(xiě)得。封裝到靠配置來(lái)進(jìn)行表格得書(shū)寫(xiě)得一步我覺(jué)得就差不多了,下面是一個(gè)小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> <!-- 序號(hào) --> <el-table-column :key="index" v-if="column.type === 'selection'" type="selection" width="55"> </el-table-column> <!-- 復(fù)選框 --> <el-table-column :key="index" v-else-if="column.type === 'index'" type="index" width="50" label="序號(hào)"> </el-table-column> <!-- 具體內(nèi)容 --> <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的時(shí)候 那么當(dāng)前格可以不顯示,可以選擇顯示自定義的slot--> <!-- 操作按鈕 --> <label v-if="column.type === 'operate'"> <a href="javascript:void(0)" rel="external nofollow" rel="external nofollow" class="operate-button" v-for="(operate, index) in column.operates" :key="index" @click="handleClick(operate, scope.row)"> {{operate.name}} </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> <!--默認(rèn)的slot --> <slot/> </el-table> </el-col> </el-row> </template>
export default { name: 'base-table', props: { // 核心數(shù)據(jù) list: { type: Array, default: () => [] }, // columns columns: { type: Array, required: true, default: () => [] } }, data () { return { tableHeight: xxx } }, methods: { // 處理點(diǎn)擊事件 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> <!-- 默認(rèn)的slot 如果交互很復(fù)雜 我們還可以直接使用表格內(nèi)部的組件 --> <el-table-column label="操作" width="200" > <template slot-scope="scope"> <a href="javascript:void(0)" rel="external nofollow" rel="external nofollow" @click="defaultSlot(scope.row)">xxx</a> </template> </el-table-column> </base-table>
export default { name: 'table-demo', data () { return { // 表格頭部配置 headers: [ { key: 'xxx', title: '測(cè)試' }, { title: 'xxx', hidden: true, slot: 'demoslot'}, { title: '操作', type: 'operate', operates: [ {name: '詳情',emitKey: 'view'} ] } ] } }, methods: { viewCb(){ // todo }, defaultSlot(){ // todo } } }
這樣封裝過(guò)的表格,應(yīng)付基本的一些需求問(wèn)題應(yīng)該不大。至于特殊的要求可以一步一步的進(jìn)行完善。
總結(jié)
這些東西并不是什么語(yǔ)法糖,是真正可以在項(xiàng)目中加快我們的效率。讓我們的自己乃至整個(gè)團(tuán)隊(duì)從繁雜的重復(fù)復(fù)制粘貼中解脫一點(diǎn)。至于速度和質(zhì)量的問(wèn)題。我是覺(jué)得使用公共組件質(zhì)量可控性會(huì)更高一些。我建議公共得東西注釋一定要寫(xiě)得全面和詳細(xì),這樣可以極大的降低我們的交流成本。至于組件的封裝還是要看你的業(yè)務(wù)。
以上觀點(diǎn)純屬個(gè)人意見(jiàn),如有錯(cuò)誤,多謝指正。
示例代碼還在整理中。。。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Vue Router路由無(wú)法跳轉(zhuǎn)問(wèn)題匯總
這篇文章主要介紹了Vue Router路由無(wú)法跳轉(zhuǎn)問(wèn)題匯總,在這里我整理了部分Vue Router路由無(wú)法跳轉(zhuǎn)問(wèn)題,結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-09-09vue.js 2.0實(shí)現(xiàn)簡(jiǎn)單分頁(yè)效果
這篇文章主要為大家詳細(xì)介紹了vue.js 2.0實(shí)現(xiàn)簡(jiǎn)單分頁(yè)效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-07-07JavaScript實(shí)現(xiàn)簡(jiǎn)單的圖片切換功能(實(shí)例代碼)
這篇文章主要介紹了JavaScript實(shí)現(xiàn)簡(jiǎn)單的圖片切換功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2020-04-04vue+springboot實(shí)現(xiàn)登錄驗(yàn)證碼
這篇文章主要為大家詳細(xì)介紹了vue+springboot實(shí)現(xiàn)登錄驗(yàn)證碼,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-05-05詳解input組合事件如何監(jiān)聽(tīng)輸入中文
這篇文章主要為大家介紹了input組合事件如何監(jiān)聽(tīng)輸入中文示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06vue-cli 使用axios的操作方法及整合axios的多種方法
這篇文章主要介紹了vue-cli 使用axios的操作方法及整合axios的多種方法,vue-cli整合axios的多種方法,小編一一給大家列出來(lái)了,大家根據(jù)自身需要選擇,需要的朋友可以參考下2018-09-09vue2.0+webpack環(huán)境的構(gòu)造過(guò)程
本文分步驟給大家介紹了vue2.0+webpack環(huán)境的構(gòu)造過(guò)程的相關(guān)資料,非常不錯(cuò)具有參考借鑒價(jià)值,需要的朋友可以參考下2016-11-11利用Vue3實(shí)現(xiàn)可復(fù)制表格的方法詳解
表格是前端非常常用的一個(gè)控件,本文主要為大家介紹了Vue3如何實(shí)現(xiàn)一個(gè)簡(jiǎn)易的可以復(fù)制的表格,文中的示例代碼講解詳細(xì),需要的可以參考一下2022-12-12