vue项目搭建以及全家桶的使用详细教程(小结)
前言
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,可以很有效的帮助我们追踪数据,定位错误。