前言
vue 是现阶段很流行的前端框架,很多人通过 vue 官方文档的学习,对 vue 的使用都有了一定的了解,但再在项目工程化处理的时候,却发现不知道改怎么更好的管理自己的项目,如何去引入一些框架以及 vue 全家桶其他框架的使用,以下将详细地介绍本人在处理工程文件构建的过程;对于刚开始解除 vue 的新手,建议使用官方脚手架 vue-cli, 当然,如果你对于 webpack 很熟悉,你也可以自己动手搭建自己的脚手架,当然如果你没把握的话,还是推荐使用 vue-cli,能更好的帮助你搭建项目:
步骤一、安装 vue-cli
首先,我们可以通过 npm 安装 vue-clic, 前提是我们需要有 node 环境,如果电脑还没安装 node,先安装,可通过
node -v
查询 node 的版本号,有版本号则已经安装成功;
接下来,我们需要确保电脑已经安装了 webpack,webpack 是一个包管理工具,也是 vue-cli 的构建工具,安装也很简单,全局安装只需要执行
npm install webpack -g
紧接着,开始我们 vue-cli 的安装
npm install –global vue-cli
查看是否安装成功,我们可以通过在 cmd 中输入 vue -V 查看,如下图出现版本号则说明安装已经完成;
我们可以打开 c 盘 > 用户 > 用户名 >AppData>Roaming>npm 查看我们全局安装的 vue-cli, 如下图:
步骤二、构建工程文件
安装完 vue-cli 后,我们可以通过在 cmd 中输入
vue init webpack projectName
生成 webpack 脚手架,在我们按下回车的时候,会出现一些提示问题,对应关系如下:
项目名称(注意名称中不要出现大写字母,否则会报错)
项目描述(可写可不写,看个人需要)
作者(可写可不写,看个人需要)
vue 编译,这个选默认即可,运行加编译 Runtime + Compiler
是否安装 vue-router 是否安装 vue 路由工具
是否使用代码管理工具 ESLint 管理你的代码
后面几个是测试的工具,需要自己自行了解 ……
紧接着,我们使用 cd squareRoot 移动到文件夹 squareRoot 下,执行
npm install
初始化项目,安装 package.json 文件中描述的依赖,初始化完成后,我们可以通过
npm run dev
运行我们的项目,这个时候,我们可以打开浏览器,输入 http://localhost:8080/,可看到如下界面,说明我们的项目脚手架已经初始化完成;
步骤三、项目结构解析
虽然我们是通过 vue-cli 生成的项目结构,但还是希望读者能够清楚的知道每个文件的作用,这样对于我们学习该脚手架以及搭建自己的脚手架会有很好的帮助,如下图,是一级目录下的文件的作用:
构建相关的代码主要是放在 build 文件夹和 config 文件夹下,包括了开发环境和生产环境,即 dev 和 product, 可以打开文件进行阅读,有接触过 node 的小伙伴应该可以很快读懂对应文件代码的作用,这里就不做详细的介绍了,需要注意的一点是,我们需要修改打包后文件的路径的时候,可以通过修改 config 文件夹下的 index.js 文件,如下图:
这里,我们需要在 src 目录下新增一个 page 文件夹,用于存放页面相关的组件,而 components 存在的是公共的组件,这样做有利于我们更好的理解项目:
步骤四、引入 UI 框架 iView
该步骤并不是一定要实现的,实际项目操作中,要根据具体需求而引入对应的 UI 框架或者不引入,鉴于指导的作用,在此处也做个示范,给与参考,可先阅读 iVew 官网学习;首先,我们应进行 iView 的安装,可利用 npm 包管理工具安装
npm install iview –save
安装成功后,我们要将对应的框架引入到项目中,这个时候,官网上有两种方法可以实现,第一种是直接在 main.js 中做如下配置:
import Vue from ‘vue’
import App from ‘./App’
import router from ‘./router’
import iView from ‘iview’;
import ‘iview/dist/styles/iview.css’;
Vue.config.productionTip = false
Vue.use(iView);
/* eslint-disable no-new */
new Vue({
el: ‘#app’,
router,
components: {App},
template: ‘<App/>’
})
这种方式是一种全局引入的方式,引入后就在具体的页面或者组件内不需要再进行其他的引入,但缺点是无论是否需要该组件,都将全部引入,对于性能优化不是很好,这里推荐第二种用法,按需引入,这里需要借助插件 babel-plugin-import 实现按需加载组件,减小文件体积。首先需要安装,并在.babelrc 中配置:
npm install babel-plugin-import –save-dev
// .babelrc
{
“plugins”: [[“import”, {
“libraryName”: “iview”,
“libraryDirectory”: “src/components”
}]]
}
然后这样按需引入组件,就可以减小体积了,这里需要注意的是,因为我们修改了.babelrc 文件,这将导致我们第一种引入方法失效了,如果再使用那种方式引入,会导致代码报错;
<template>
<div class=”content”>
<div class=”title”> 患者接诊 </div>
<div>
<Button type=”primary” shape=”circle” class=”btn-time”> 临时保存 </Button>
<Button type=”primary” shape=”circle” class=”btn-cancel”> 取消就诊 </Button>
<Button type=”primary” shape=”circle” class=”btn-done”> 完成就诊 </Button>
</div>
</div>
</template>
<script>
import {Button} from ‘iview’
export default {
name: “fHeader”,
components:{
Button
}
}
</script>
运行结果如下图
步骤五、vue-router 的使用
如果没有阅读过官方文档,建议大伙先阅读,官网上的教程已经足够详细,受益匪浅;学习的过程中,需要了解路由配置的基本步骤,命名规则,嵌套路由,路由传参,具名视图以及路由守卫,滚动行为和懒加载,这里我们就不一一详细介绍了,官网已有,我们这里是做构建是的配置和懒加载处理:首先,我们应该是安装 vue-router, 这个在我们生成项目的时候,已经将该依赖加载进来了,下一步要做的是在 router 文件下 index.js 进行配置:
import Vue from ‘vue’
import Router from ‘vue-router’
Vue.use(Router)
export default new Router({
scrollBehavior (to, from, savedPosition) {
return {x: 0, y: 0}
},
routes: [
{
path:’/’,
redirect:’/root’
},
{
path: ‘/root’,
name: ‘root’,
components: {
Left:() => import(‘@/page/rootLeft.vue’),
Middle: () =>import(‘@/page/rootMiddle.vue’),
Right: ()=>import(‘@/page/rootRight.vue’)
}
}
]
})
上面的代码中,我们应用到了几个知识点,首先是滚动行文,这里我们配置了当路由跳转的时候,默认是滚动到(0,0)位置,即页面开始位置,其次我们用到的 redirect 是一个路由重定向的配置,接下来,在路由 ”/root” 下,配置了具名视图,加载对应组件到对应视图,我们引入组件的方式使用到了箭头函数,这样写的目的是为了实现路由的懒加载,这样构建,只有在路由被执行的时候,才有引入对应的组件,对于页面性能的优化有很大的帮助;这里还需要注意的是,我们在引入的这些组件中,其实默认都是打包到一个文件下的,这样就会导致一次性引入的文件过大,为此,我们可以利用 webapck 打包工具,我们在 build>webpack.base.conf.js 文件下,增加如下代码,用于配置输出文件的模块名称,[name] 是文件的名称,[chunkhash] 是打包文件的哈希值,加上这个是为了将其作为版本号,以解决浏览器缓存机制带来的问题:
然后在路由文件中引入组件的代码如下:
{
path:”/test”,
name:”test”,
component: ()=>import(/*webpackChunkName:”test”*/’@/page/test.vue’)
}
在引入组件的时候,加上 / webapckChunkName: “ 文件名 ” /,就这可以将对于的组件打包到指定名称的文件下,这样可以减少首次加载的文件的大小,对于一些没有联系的功能,比如不同页面,我们可以把对应的组件放在同一个文件,这样,既可以减少首次加载文件达大小,同时也可以将文件实现一个按需加载,提高页面性能;
通过控制台,我们可以查看当前加载的文件资源,当我们点击测试按钮的时候,页面发生的跳转,这时候,我们会发现,在 Network 下,会加一条新的资源加载信息,这一条就是我们的分块打包后请求的资源;
步骤六、全局过滤器 filter 和全局注册组件的引入
写到这里的时候,可能很多人都会觉得,全局注册 filter 和全局组件组件不是很简单吗,直接 Vue.filter() 和 Vue.component() 不久解决了吗,其实这么讲也没错,但是你可曾想过,注册全组件是挂载在 Vue 对象下的,这意味这按照正常思路,我们要写在 main.js 文件下,这样就会造成,我们所写的 mian 文件过于冗长,你可以想一下,把全局的过滤器,和组件都写进去,着实丑陋,很不优雅,下面跟大家说一个优雅的实现方法:首先,我们在 src>assets 目录下新建一个 js 文件夹,再该文件夹下再创建一个 filters.js 的文件,如下图:
接下来,我们在 filters.js 文件下写我们的全局过滤器,再将其抛出,写一个时间过滤器作为例子:
const fullTime = val => {
var dateObj = new Date(Number(val));
var year = dateObj.getFullYear();
var month =
dateObj.getMonth() + 1 > 9
? (dateObj.getMonth() + 1).toString()
: “0” + (dateObj.getMonth() + 1).toString();
var date =
dateObj.getDate() > 9
? dateObj.getDate().toString()
: “0” + dateObj.getDate().toString();
var hour =
dateObj.getHours() > 9
? dateObj.getHours().toString()
: “0” + dateObj.getHours().toString();
var minutes =
dateObj.getMinutes() > 9
? dateObj.getMinutes().toString()
: “0” + dateObj.getMinutes().toString();
return year + “/” + month + “/” + date + ” ” + hour + “:” + minutes;
};
module.exports={
fullTime
}
做完这一步,其实我们的过滤器还没写完,还需要在 main.js 中写一个注册函数:
import Vue from ‘vue’
import App from ‘./App’
import router from ‘./router’
import filters from ‘./assets/js/filters’
import ‘iview/dist/styles/iview.css’;
Object.keys(filters).forEach(key =>{
Vue.filter(key,filters[key])
})
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: ‘#app’,
router,
components: {App},
template: ‘<App/>’
})
这样,我们就把 filters 文件下的过滤器函数注册到 Vue 全局下,同样道理,我们可以按照同样的思路注册全局组件,我们在 src>assets>js 下新建一个 components.js 文件,在其中引入我们想注册的全局组件,export 出一个对象,使用 Object.keys 获取后注册到全局下:
//components.js 下
import testInput from ‘@/components/testInput.vue’
export default{
testInput:testInput
}
//main.js 下
import components from ‘./assets/js/components’
Object.keys(components).forEach(key => {
Vue.component(key,components[key])
})
优雅的注册全局组件和全局过滤器已经讲完,接下来就是 API 管理阶段了。
步骤七、请求 api 管理
这里我们使用 axios 发起异步的请求,安装很简单,npm install axios 即可,一开始的时候,我使用的是直接在每个组件内使用 axios, 到后面发现,但当我需要修改 api 接口的时候,需要查找的比较麻烦,只因为没有集中的对所有的 api 进行管理,而且每个请求回来的接口都需要写对应的报错处理,着实麻烦,这里我新建一个 fecth 文件夹并在其下新建一个 api.js 用来存放所有的 axios 处理和封装,:
//fetch/api.js
import axios from ‘axios’
export function fetch(url, params) {
return new Promise((resolve, reject) => {
axios.post(url, params).then(
response => {
resolve(response.data)
}
).catch(error => {
console.log(error)
reject(error)
})
})
}
getDefaultData=()=>{
return fetch(‘/api/getBoardList’);
}
export default {
getDefaultData
}
这样做的好处是集中化的管理了所有的 api 接口,当我们需要修改接口相关的代码,只需要在 api.js 中修改,包括路由修改以及路由拦截等,可读性更好;在不同的组件内,我们只需要把对应的接口用解构赋值的思想把它引入对应的组件内即可使用。
import {getDefaultData} from ‘@/fetch/api.js’
步骤八、代理服务器的配置
这个功能主要是我们在调试接口的时候使用,因为当我们运行 npm run dev 的时候,实际上我们的项目已经挂载在一个本地服务端运行了,端口号为我们配置的 8080,当我们想在该项目下访问服务端接口数据的时候,就会产生跨域的问题,这个时候,我们就需要使用到 proxy 代理我们的数据请求,在 vue-cli 中已有配置相关的代码,我们仅需要把对应的代理规则写进去即可,这里以一个通用配置例子实现; 首先,我们在 fetch 文件夹下新建一个 config.js 的文件,用于存放我们的代理路径配置:
const url = ‘http://www.dayilb.com/’;
let ROOT;
if (process.env.NODE_ENV === ‘production’) {
// 生产环境下的地址
ROOT = url;
} else {
// 开发环境下的代理地址,解决本地跨域跨域,配置在 config 目录下的 index.js dev.proxyTable 中
ROOT = “/”
}
exports.PROXYROOT = url; // 代理指向地址
exports.ROOT = ROOT;
接下来,我们要在 config 目录下新建一个 proxyConfig.js, 存放代理服务器的配置规则:
var config= require(“../src/fetch/config”);
module.exports = {
proxy: {
[config.ROOT]: {// 需要代理的接口,一般会加前缀来区分,但我个人是没加,即‘/’都转发代理
target: config.PROXYROOT, // 接口域名
changeOrigin: true, // 是否跨域
pathRewrite: {
[`^/`]: ” // 需要 rewrite 的,针对上面的配置,是不需要的
}
}
}
}
最后,我们在 config 目录下的 index.js 文件中,引入我们的代理规则, 并在,即
var proxyConfig=require(‘./proxyConfig’)
…// 省略号表示省略其他代码
module.exports = {
…
proxyTable: proxyConfig.proxy,
…
}
重新启动项目,我们就可以做到代理转发来实现跨域请求了。
步骤九、vuex 状态管理引入
终于,来到了最后一步,那就是我们的状态管理 vuex,其实这个东西不是说所有项目都需要引入,看项目的具体需求,但需要对同一个数据源进行大量的操作的时候,建议使用,如果每个组件的数据都可以轻易的在 data 中管理,那其实是没必要引进去的,该管理工具是更友好的解决了组件间传值的问题,包括了兄弟组件;首先,我们需要安装 vuex, 老规矩就是
npm install vuex
安装完成后,我们需要对我们的项目进行一些修改,首先是我们的目录,我们需要 src 下新增一个 store 文件夹作为 vuex 数据存放位置,在开始搭建前,我们需要有 vuex 的相关知识,我就不一一说明,自行百度一下 vuex 官方文档;众所周知,vuex 有 state,getter,mutation,action 等关键属性,state 主要是用于存放我们的原始数据结构,类似与 vue 的 data, 不过它是全局的,getter 类似于计算属性 computed,mutation 主要用于触发修改 state 的行为,actions 也是一种触发动作,只不过与 mutation 的区别在于异步的操作我们只能在 action 中进行而不能在 mutation 中进行,目的是为了浏览器更好的跟踪 state 中数据的变化。接下来,我们来看一下 store 文件夹中都有什么:
从上图可知,我创建了一个 index.js 入口文件,getters.js,mutation.js 和 mutationtypes.js, 以及 actions.js,下面我们先看看 index.js 的源码:
import Vue from ‘vue’
import Vuex from ‘vuex’
import actions from ‘@/store/actions.js’
import getters from ‘@/store/getters.js’
import mutations from ‘@/store/mutations.js’
Vue.use(Vuex)
const state = {
recipeList:[],
currRecipe:0
};
if (module.hot) {
// 使 action 和 mutation 成为可热重载模块
module.hot.accept([‘./mutations’], () => {
// 获取更新后的模块
// 因为 babel 6 的模块编译格式问题,这里需要加上 `.default`
const newMutations = require(‘./mutations’).default;
// 加载新模块
store.hotUpdate({
mutations: newMutations,
})
})
}
export default new Vuex.Store({
state,
mutations,
getters,
actions
})
首先,我们把 Vuex 插件引入 vue 中,并新建了一个 Vuex.Store() 对象,其中各项属性值来自我们前面所创建的文件夹,中间 module.hot 是配置我们的 action 和 mutation 成为可热重载的模块,对于我们的调试更方便,当我们创建为 Vuex.store 对象后,我们还需要把它声明到 main.js 的页面 Vue 对象中
import store from ‘./store/index’
…
new Vue({
el: ‘#app’,
router,
store,
components: {App},
template: ‘<App/>’
})
在使用 mutation 的时候,我们是推荐大家把所有的行为常量保存到一个.js 文件中,这样更有利于管理我们的项目,因为我们的 mutation 往往是需要使用 action 进一步封装的,这样我们在使用的时候,只需要修改常量对象里的属性值,就可以达到同时修改 mutation 和 action 的对应关系,一举两得,下面举例给大家参考:
//mutationType.js
export default {
ADD_NEW_RECIPT:’ADD_NEW_RECIPT’,
CHANGE_CURR_TAB:’CHANGE_CURR_TAB’
}
//mutations.js
import mutationTypes from ‘@/store/mutationTypes.js’
const mutations = {
[mutationTypes.ADD_NEW_RECIPT](state, item) {
state.recipeList.push(item);
},
[mutationTypes.CHANGE_CURR_TAB](state, index) {
state.currRecipe=index;
}
}
;
export default mutations
import mutationTypes from ‘@/store/mutationTypes.js’
const actions = {
add_new_recipt:({commit,state}, type)=>{
commit(mutationTypes.ADD_NEW_RECIPT,type);
},
change_curr_tab:({commit},index)=>{
commit(mutationTypes.CHANGE_CURR_TAB,index)
}
};
export default actions
从上面的例子可以看出,action 和 mutation 使用的是同一个常量表,可以更好的管理我们的修改动作,而不会出现对不上的错误;最后,我们在组件内引入 vuex 中存放的 state 和 action,如下
import {mapActions, mapState} from ‘vuex’
…
computed: {
…mapState({
recipeList: state => state.recipeList,
currRecipe: state => state.currRecipe
})
},
methods: {
…mapActions([
‘add_new_recipt’,
‘change_curr_tab’
]),
addNewRecipt(type) {
this.add_new_recipt(type)
}
}
这里是推荐大家按照例子中,使用 mapActions 和 mapState 以及利用三点扩展符来引入 state 和 action,state 最好存放在组件的 computed 属性内,这样当 state 中的数据发生改变的时候,也会实时的修改 computed 里定义的变量值,来实现数据的绑定,同时,当我们修改了某些数据的时候,也要同步到 state 中去,这样数据源才可以保持一致性与准确性;
总结
写这个的时候,只是给个思路去搭建自己的工程文件,并不是说把所有相关知识点都讲一遍,需要有一定的相关知识,不过相信还没自己搭建过工程文件的小伙伴会不知道如何去安排,可以参考参考,这里推荐大家安装 chrome 的扩展插件 Vue.js devtools,可以很有效的帮助我们追踪数据,定位错误。