加快Vue项目的开发速度的方法


Posted in Javascript onDecember 12, 2018

现如今的开发,比如是内部使用的管理平台这种项目大都时间比较仓仓促。实际上来说在使用了webpack + vue 这一套来开发的话已经大大了提高了效率。但是对于我们的开发层面。还是有很多地方可以再次提高我们的项目开发效率,让我们更加专注于业务,毕竟时间就是生命。下面我们挨个来探讨。

巧用Webpack

Webpack是实现我们前端项目工程化的基础,但其实她的用处远不仅仅如此,我们可以通过Webpack来帮我们做一些自动化的事情。首先我们要了解require.context()这个API

require.context()

您可以使用require.context()函数创建自己的上下文。 它允许您传入一个目录进行搜索,一个标志指示是否应该搜索子目录,还有一个正则表达式来匹配文件。

其实是Webpack通过解析 require() 的调用,提取出来如下这些信息:

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

然后来创建我们自己的上下文,什么意思呢,就是我们可以通过这个方法筛选出来我们需要的文件并且读取

下面我们来简单看一看使用:

/**
* @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 请求到的文件的上下文。

不要困惑,接下来我们来探讨在项目中怎么用。

组织路由

对于Vue中的路由,大家都很熟悉,类似于声明式的配置文件,其实已经很简洁了。现在我们来让他更简洁

分割路由

首先为了方便我们管理,我们把router目录下的文件分割为以下结构

router              // 路由文件夹
 |__index.js          // 路由组织器:用来初始化路由等等
 |__common.js          // 通用路由:声明通用路由
 |__modules           // 业务逻辑模块:所以的业务逻辑模块
    |__index.js       // 自动化处理文件:自动引入路由的核心文件
    |__home.js        // 业务模块home:业务模块
    |__a.js         // 业务模块a

modules文件夹中处理业务模块

modules文件夹中存放着我们所有的业务逻辑模块,至于业务逻辑模块怎么分,我相信大家自然有自己的一套标准。我们通过上面提到的require.context()接下来编写自动化的核心部分index.js

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
    }
   ]
  }
]

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')
 }
]

路由初始化

这是我们的最后一步了,用来初始化我们的项目路由

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拿出来作为一个单独文件。

全局组件统一声明

同样的道理,有了上面的经验,我们照葫芦画瓢来处理一下我们的全局组件。这就没什么可说的了,直接上核心代码

组织结构

components            // 组件文件夹
 |__xxx.vue           // 其他组件
 |__global           // 全局组件文件夹
    |__index.js       // 自动化处理文件
    |__demo.vue       // 全局demo组件

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)
})

使用和说明

这个使用起来就更简单了,直接在app.js引用这个文件就行。

注意:我之前看到有些人做法是使用组件名去区分全局组件和普通组件,然后通过正则去判断需不需要全局注册。我是直接把全局的组件放到global文件夹下,然后组件的注册名称直接使用component.name。至于使用哪种方式就比较看个人了。

充分利用NodeJS

放着node这么好得东西不用真是有点浪费,那么我们来看看node能为我们增加效率做出什么贡献。

有这么一个场景,我们每次创建模块的时候都要新建一个vue文件和对应的router配置,而且新页面的大部分东西都还差不多,还得去复制粘贴别得页面。这想想就有点low。那既然有了node我们可不可以通过node来做这写乱七八糟得事情? 下面来把我们的想法付诸于显示。

我们实现这个功能主要要借助Node的fs和process, 感兴趣的话可以深入研究一下。

首先我们要编写我们的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')
  })

下面我们看使用的流程

加快Vue项目的开发速度的方法

这样我们就分别创建了vuerouter的文件,而且已经注入了内容。按照我们提前声明的组件

注意:这只是一个简单的思路,通过Node强大的文件处理能力,我们能做的事情远不止这些。

发挥Mixins的威力

Vue中的混入mixins是一种提供分发 Vue 组件中可复用功能的非常灵活的方式。听说在3.0版本中可能会用Hooks的形式实现,但这并不妨碍它的强大。基础部分的可以看这里。这里主要来讨论mixins能在什么情景下帮助我们。

比如我们的大量的表格页面,仔细一扒拉你发现非常多的东西都是可以复用的例如分页表格高度加载方法laoding声明等一大堆的东西。下面我们来整理出来一个简单的list.vue

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()
 }
}
</script>

使用了mixins之后一个简单的有loadoing, 分页,数据的表格大概就只需要上面这些代码。

注意: <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)" 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>
    <!--默认的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)" 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: '测试' },
    { title: 'xxx', hidden: true, slot: 'demoslot'},
    {
     title: '操作', type: 'operate',
     operates: [
      {name: '详情',emitKey: 'view'}
     ]
    }
   ]
  }
 },
 methods: {
  viewCb(){
   // todo
  },
  defaultSlot(){
   // todo
  }
 }
}

这样封装过的表格,应付基本的一些需求问题应该不大。至于特殊的要求可以一步一步的进行完善。

总结

这些东西并不是什么语法糖,是真正可以在项目中加快我们的效率。让我们的自己乃至整个团队从繁杂的重复复制粘贴中解脱一点。至于速度和质量的问题。我是觉得使用公共组件质量可控性会更高一些。我建议公共得东西注释一定要写得全面和详细,这样可以极大的降低我们的交流成本。至于组件的封装还是要看你的业务。

以上观点纯属个人意见,如有错误,多谢指正。

示例代码还在整理中。。。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
js实现简单模态窗口,背景灰显
Nov 14 Javascript
JavaScript入门教程(8) Location地址对象
Jan 31 Javascript
JavaScript 判断判断某个对象是Object还是一个Array
Jan 28 Javascript
一些经常会用到的Javascript检测函数
May 31 Javascript
Javascript算符的优先级介绍
Mar 20 Javascript
js给onclick事件赋值,动态传参数实例解说
Mar 28 Javascript
javascript感应鼠标图片透明度显示的方法
Feb 24 Javascript
javascript动画算法实例分析
Jul 31 Javascript
JavaScript实现QQ列表展开收缩扩展功能
Oct 30 Javascript
Thinkjs3新手入门之添加一个新的页面
Dec 06 Javascript
vue router的基本使用和配置教程
Nov 05 Javascript
Vue全局loading及错误提示的思路与实现
Aug 09 Javascript
关于自定义Egg.js的请求级别日志详解
Dec 12 #Javascript
JS/HTML5游戏常用算法之碰撞检测 像素检测算法实例详解
Dec 12 #Javascript
d3绘制基本的柱形图的实现代码
Dec 12 #Javascript
JS/HTML5游戏常用算法之碰撞检测 地图格子算法实例详解
Dec 12 #Javascript
JS/HTML5游戏常用算法之追踪算法实例详解
Dec 12 #Javascript
js使用swiper实现层叠轮播效果实例代码
Dec 12 #Javascript
如何制作一个Node命令行图像识别工具
Dec 12 #Javascript
You might like
法兰绒滤网冲泡
2021/03/03 冲泡冲煮
使用php实现下载生成某链接快捷方式的解决方法
2013/05/07 PHP
析构函数与php的垃圾回收机制详解
2013/10/28 PHP
PHP基于数组实现的分页函数实例
2014/08/20 PHP
php 广告点击统计代码(php+mysql)
2018/02/21 PHP
表格 隔行换色升级版
2009/11/07 Javascript
IE8 中使用加速器(Activities)
2010/05/14 Javascript
JavaScript中SetInterval与setTimeout的用法详解
2015/11/10 Javascript
理解javascript中的with关键字
2016/02/15 Javascript
js控件Kindeditor实现图片自动上传功能
2020/07/20 Javascript
使用JavaScript解决网页图片拉伸问题(推荐)
2016/11/25 Javascript
Javascript this 函数深入详解
2016/12/13 Javascript
JS中parseInt()和map()用法分析
2016/12/16 Javascript
JS常用知识点整理
2017/01/21 Javascript
vue2.X组件学习心得(新手必看篇)
2017/07/05 Javascript
浅谈ajax在jquery中的请求和servlet中的响应
2018/01/22 jQuery
vue2.0+vuex+localStorage代办事项应用实现详解
2018/05/31 Javascript
详解Vue中使用Echarts的两种方式
2018/07/03 Javascript
[15:39]教你分分钟做大人:龙骑士
2014/10/30 DOTA
python使用cPickle模块序列化实例
2014/09/25 Python
编写Python脚本把sqlAlchemy对象转换成dict的教程
2015/05/29 Python
浅谈python中copy和deepcopy中的区别
2017/10/23 Python
Python模糊查询本地文件夹去除文件后缀的实例(7行代码)
2017/11/09 Python
python中urlparse模块介绍与使用示例
2017/11/19 Python
深入理解Python中的 __new__ 和 __init__及区别介绍
2018/09/17 Python
Python将json文件写入ES数据库的方法
2019/04/10 Python
pyqt5实现按钮添加背景图片以及背景图片的切换方法
2019/06/13 Python
Python如何计算语句执行时间
2019/11/22 Python
Django中从mysql数据库中获取数据传到echarts方式
2020/04/07 Python
基于PyInstaller各参数的含义说明
2021/03/04 Python
实习教师自我鉴定
2013/12/12 职场文书
群众路线剖析材料怎么写
2014/10/09 职场文书
2014年学校后勤工作总结
2014/12/06 职场文书
幼儿园辞职书
2015/02/26 职场文书
中小学生安全教育观后感
2015/06/17 职场文书
Python进行区间取值案例讲解
2021/08/02 Python