写这篇文章的目的,更可能是让本身更熟悉vue-cli脚手架建立项目的依据和项目结构,其次是但愿个人学习过程能够帮到有疑惑的同窗,有什么错误还但愿能够获得指教css
为何要分上、下,由于最近学习react.js,发现项目框架除了使用的js库不一样(vue.js、react.js),配置基本上是大同小异的html
这也是我占坑(脸大)的理由vue
此次的目的是接着上篇在框架中添加vue-router,在第三方工具包的基础上针对业务进行封装html5
路由是vue组件可以灵活切换的关键所在,vue-router是vue的官方路由。node
cnpm i vue-router --save-dev
复制代码
咱们在src目录下新建router文件夹,并在router新建index.js,同时在components下新建login.vuereact
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const router = new VueRouter({
mode: "hash",
routes: [
{
path: "/",
name: "index",
components: require("../components/hello.vue")
},
{
path: "/login",
name: "login",
components: require("../components/login.vue")
}
]
})
export default router
复制代码
到这里还没结束,咱们还须要将router对象挂载到根实例上,在index.js中,以下操做webpack
import router from "./router/index";
new Vue({
el: "#app",
router,
render: h => h(App)
});
复制代码
在你须要渲染组件的地方添加router-view标签ios
<template>
<div>
<router-view />
</div>
</template>
复制代码
如今你能够在浏览器中访问了es6
可是有时候咱们的项目要求没有登录是不能进入首页的,这个功能能够利用router的前置导航守卫实现web
beforeEach就是前置导航守卫的钩子函数,它接收三个参数,to、from、next
我使用了h5的localStorage模拟cookie保存用户信息,这里只是个测试,若是你喜欢cookie,可使用本身喜欢的cookie包,我的喜欢js-cookie
思路是这样的,登陆的时候验证完对的用户名和密码,就设置一个字符串做为token存储在本地,token的做用就是下次能够免登陆(这种作法的安全性是个问题)
在router/index.js中
router.beforeEach((to, from, next) => {
// 若是登陆的时候设置过
if(localStorage.getItem("token") != null){
if(to.name == 'login'){// 若是还访问登陆页就导向首页
next({path: '/'})
}else{// 给全部其它页面放行
next()
}
}else{// 若是没有设置这个值,为空,说明没有登陆,导向登陆页
if (to.name == "login") {
next();
} else {
if (to.name == "login") {
next(); // 这里要记得给登陆页放行,否则会进入死循环
} else {
next({ path: "/login" });
}
}
}
})
复制代码
虽然这里已经作得很不错了,但依然没有发挥出vue-router灵活和强大的一面。换句话说,这里能作的事还有不少不少,好比项目是一个管理系统的话,可能会因角色不一样,进入首页的侧边栏目是不同的,虽然看上去是个很复杂的过程,可是仔细分析下也就几步,感兴趣的同窗能够看这里
同级路由就是两个router-view标签是并列的,分别给两个标签用name属性命名,因此命名视图就是给视图命名了后的视图router-view
// app.vue
<router-view name="navbar"></router-view>
<router-view name="main"></router-view>
// 对应的路由写法就是
import { NavbarComponent, MainComponent } from "@/components"
const router = new VueRouter({
routes: [
{
path: '/',
components: {
navbar: NavbarComponent,
main: MainComponent
}
}
]
})
// 在app.vue中
<template>
<div>
<router-view name="navbar" />
<router-view name="main" />
</div>
</template>
复制代码
这时你看到的是这样的
嵌套路由在管理系统更为常见,咱们常用的layout布局就是嵌套路由实现的
// 此次我这样定义路由
{
path: "/index",
name: "index",
components: require("@/components/hello"),
children: [
{
path: "login",
name: "login",
components: require("@/components/login")
}
]
},
// 而且这样写hello组件
<template>
<div class="test">index page
<router-view></router-view>
</div>
</template>
复制代码
我在浏览器中访问http://localhost:8080/#/index/login,能够访问到下面的页面
官方推荐与服务端交互使用axios,它是基于ajax封装的,用起来十分简洁
cnpm i axios --save-dev
复制代码
axios有两种使用方法一种是使用axios对象调用get或者post请求方法, 另外一种是使用axios api,使用axios()函数,参数是个配置对象options
axios.get('/getUser')
.then(data=>{
// 请求成功处理函数
console.log(data)
})
.catch(err=>{
// 请求失败处理函数
console.log(err)
})
// 或者
axios({
url: '/getUser',
mothod: 'get'
})
.then(data=>{
// 请求成功处理函数
console.log(data)
})
.catch(err=>{
// 请求失败处理函数
console.log(err)
})
复制代码
在实际的项目中,常常须要对发送的请求或者服务端响应的结果进行处理,请求不少时,挨个处理就很繁琐,也很不切合实际,但愿在全局就已经处理好了,咱们只负责发送请求和接收服务端响应。
好在axios提供了这种方法。
在src下新建http文件夹,下面新建request.js文件
import axios from "axios";
// 从新实例化一个axios实例,这个实例会覆盖全部默认属性
const server = axios.create({
baseURL: "/api",
timeout: 5000,
heads: { 'content-type': 'application/x-www-form-urlencoded' },
});
// 或者经过修改实例的defaults属性,这两种方法是等价的
server.defaults.baseURL = "/api";
server.defaults.timeout = 5000;
export default server
复制代码
不只如此,咱们还但愿在每次发送请求的时候带上登陆是设置的token值,在收到服务器错误时能够作出相应的反馈,好比返回的状态码为404,就导航到404页面
这里可使用axios提供的拦截器对象,具体作法以下
// 设置拦截器
// 请求拦截器
server.interceptors.request.use(
config => {
config.headers.token = localStorage.get("token");
return config;
},
err => {
return Promise.reject(err);
}
);
// 响应拦截器
server.interceptors.response.use(
response => {
return response;
},
err => {
switch (err.response.status) {
case 404:
router.push({
path: "/404"
});
break;
case 504:
router.push({
path: "/504"
});
break;
}
return Promise.reject(err);
}
);
复制代码
++记得添加错误对应的页面和路由++
// router/index.js
{
path: "/404",
name: "404",
components: require("../components/404.vue")
},
{
path: "/504",
name: "504",
components: require("../components/504.vue")
}
复制代码
简单的封装已经完成了,只须要在使用的地方引入它
这里咱们就得说说跨域了,这是先后端分离项目没法避开的。对于先后端分离的项目,之因此跨域是由于浏览器有同源策略的安全限制,来防止跨站请求伪造和跨站脚本攻击
跨域就是违背了同源策略,webpack-dev-server提供了解决跨域的方案
// webpack-dev-server方案
// 在webpack.config.js中devServer
devServer: {
port: 8080,
proxy: {
"/api": {
target: "http://127.0.0.1:10000", // 代理的目标地址
changeOrigin: true // 是否开启跨域
}
}
}
复制代码
固然axios也提供了跨域方案,只是比较复杂,还须要后端同窗的设置配合才行,这里就不说了
这里使用一个例子来讲明使用方法。==固然,我没有写登陆功能,因此记得把以前写的路由前置守卫的钩子函数注释掉,否则没法跳转==
逻辑是这样的,在接入首页时自动发送请求,获取用户列表,请求方式为get
// hello.vue
import axios from '../http/request.js'
export default {
created() {
axios({
url: '/getUsers',
method: 'get'
})
.then(data =>{console.log(data)})
.catch(err =>{console.log(err)})
}
};
复制代码
若是你已经有后端服务器的支持,可是没有写对应的路由,那它会在控制台报错超时,即状态码为504,页面会自动跳转到504页面
假如你和我同样没有后端服务程序,那控制台报错为404,即状态码为404,页面会自动跳转到404页面
到这里项目框架功能已经算是撸完了,可是它有不少地方不够正规,和cli建立的项目相比,生产环境和开发环境尚未分离;其次一个合格的大众框架,应该有eslint语法检测;虽然主流的浏览器已经开始支持es6测试语法,可是最好能够加入babel-loader,将es6语法转换es5语法。
babel-loader主要是将es6语法转换成浏览器兼容的es5语法,可是项目中node_moudles下也有不少js文件,全都转换会使得速度变慢,而且使项目没法运行,因此须要配置转换文件的路径或者屏蔽掉无需转换的路径
babel-loader只是个加载器,转换代码的工做是babel-core babel在转换 ES2015 语法为 ECMAScript 5 的语法时,babel 会须要一些辅助函数, 例如 _extend。babel 默认会将这些辅助函数内联到每个 js 文件里,这样文件多的时候,项目就会很大。
因此 babel 提供了 transform-runtime 来将这些辅助函数“搬”到一个单独的模块 babel-runtime 中,这样作能减少项目文件的大小
// babel-preset-env这是babel预设的语法规则,babel-preset-是前缀,env是包名
// @babel/plugin-transform-runtime是插件,babel-plugin-是前缀,transform-runtime是插件名
cnpm i babel-core babel-loader babel-preset-env @babel/plugin-transform-runtime --save-dev
复制代码
==这里要注意下载的版本,起初我下载的是babel-plugin-transform-runtime,结果一直报错==
TypeError: this.setDynamic is not a function
复制代码
后来我下载了@babel/plugin-transform-runtime才解决了这个问题
// rules中
// include 表示哪些目录中的 .js 文件须要进行 babel-loader
// exclude 表示哪些目录中的 .js 文件不要进行 babel-loader
module: {
rules: [
{
test: /\.js$/,
loader: "babel-loader",
// include: [path.resolve("src")],
exclude: /node_modules/,
options: {
presets: ['env'], // env提供语法转化的规则,这里是babel预设的
plugins: ['transform-runtime'] // 这里放咱们本身想要使用的插件
}
}
]
}
复制代码
babel-loader默认是什么都不会作的,须要在预设presets选项中指定插件为你工做的语法规则,babel已经为咱们提供了几套预设方案,babel-preset-env就是最新的语法转换规则,其中babel-preset-只是前缀, 另外babel-loader还能够提供丰富的插件作更多的事,只须要在options下的plugins选项中指明
++注意:要用的插件必定要记得下载++
下面除了想自动语法转化外还但愿将组件懒加载,使加载变快,这时候须要使用插件babel-plugin-syntax-dynamic-import,因此先下载这个包,而后将它放入plugins中
// 下载
cnpm i babel-plugin-syntax-dynamic-import --save-dev
// 在module选项下
rules: [
{
test: /\.js$/,
loader: "babel-loader",
include: [path.resolve("src")],
exclude: [path.resolve("node_modules")],
options: {
presets: ['env'], // env提供语法转化的规则,这里是babel预设的
plugins: ['@babel/transform-runtime',"syntax-dynamic-import"]
}
}
]
复制代码
假如你须要的插件不少,还须要单独的配置,你能够在项目根目录下建立一个.babelrc的文件替换掉options,配置语法彻底同json,下面给个示例,彻底等同上面的options
// 在.babelrc文件中,必须是要有两个数组类型的选项:presets、plugins
{
"presets": ["env"],
"plugins": ["@babel/transform-runtime", "syntax-dynamic-import"]
}
复制代码
presets更多选项看这里
eslint对初学者来讲是极其不友好的,由于它有严格的要求,使得不少人都想着关闭它,可是不得不说eslint在多人协做团队来讲是相当重要的,它能够保证代码风格的一致性
cnpm i eslint-plugin-vue eslint-friendly-formatter eslint-loader eslint --save-dev
// 此外记得全局安装eslint支持vue的插件,不然会报错Cannot find module 'eslint-plugin-vue',可是局部仍然要安装
cnpm i eslint-plugin-vue -g
复制代码
在webpack配置文件中,添加新的rules
{
test: /\.(js|vue)$/,
loader: "eslint-loader",
include: [path.resolve(__dirname, 'src')], // 指定检查的目录
options: { // 这里的配置项参数将会被传递到 eslint 的 CLIEngine
formatter: require('eslint-friendly-formatter') // 指定错误报告的格式规范
}
}
复制代码
eslint官网提到了三种使用方法(这也是查找规则所在位置的顺序)
我选择效仿别人使用.eslintrc.js形式,既然是js文件咱们就须要跟写js模块文件同样暴露出一个对象
// 像这样
module.exports = {}
复制代码
这里我只说几个经常使用的或者重要的配置选项,官网支持中文版,比babel中文版友好不少
// 像这样
module.exports = {
// 默认状况下,ESLint 会在全部父级目录里寻找配置文件,一直到根目录。若是你想要你全部项目都遵循一个特定的约定时,这将会颇有用,但有时候会致使意想不到的结果。为了将 ESLint 限制到一个特定的项目,在你项目根目录下的 package.json 文件或者 .eslintrc.* 文件里的 eslintConfig 字段下设置 "root": true。ESLint 一旦发现配置文件中有 "root": true,它就会中止在父级目录中寻找。
"root": true,
// extends: 继承属性,值能够是 "eslint:recommended" "eslint:all" 或者是个插件 或者是个文件
extends:[
"eslint:recommended",
"eslint:all"
// "plugin:react/recommended"
],
// env: 指定运行的环境,可选值有browser、node、es6等等,值为布尔类型,true为开启,默认为false
"env": {
"browser": true
},
// plugins: 指定插件,使用以前需下载,vue项目中使用的话vue就是属于插件
"plugins": [
"vue"
// "react"
],
// rules: 指定语法规则,分为0,1,2三个等级对应off(关闭检测),warn(只警告),error(直接报错)
"rules": {
"eqeqeq": "off",
"curly": "error", // if结构中必须使用{}
"quotes": ["error", "double"]
// 更多rules看这里 https://www.jianshu.com/p/80267604c775
// 这里要说下rules的extends特性
// 假如你的设置是这样的
// "eqeqeq": ["error", "allow-null"]
// "eqeqeq": "warn"
// 那么它最后生成的配置是 "eqeqeq": ["warn", "allow-null"]
},
}
复制代码
优秀的框架确定会根据配置文件查看是否开启eslint,我在配置文件中定义了一个可配置的变量esLint,当它的值为true时开启eslint语法检测,但默认值为false
const esLint = false
// 替换掉配置好的eslint-loader
....
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
},
esLint?{
test: /\.(js|vue)$/,
loader: "eslint-loader",
include: [path.resolve(__dirname, "src")], // 指定检查的目录
options: {
// 这里的配置项参数将会被传递到 eslint 的 CLIEngine
formatter: require("eslint-friendly-formatter") // 指定错误报告的格式规范
}
}:{},
...
复制代码
项目里我一直使用的是相对路径,虽然有友好的代码提示,可是一旦你改变了文件位置,就会报错不止,直到你把全部路径修改正确,最好的作法就是使用绝对路径,而且给路径添加别名,能够方便咱们的书写
resolve: {
alias: {
···
"@": resolve("src")
}
}
复制代码
这样咱们在import中书写路径时能够用@表示根目录到src,后面继续跟剩下的路径
import hello from "@/components/hello.vue";
复制代码
resolve: {
extensions: [".js", ".vue", ".json"], // 固然,这里还能够添加.css、.less、.sass,这都是容许的
···
}
复制代码
如今你在import时有.js或者.vue文件时不用再写后缀名了
import hello from "@/components/hello";
复制代码
花了两天时间终于把框架撸完了,说实话,之前没有在乎的细节如今都很通透,固然这对我来讲只是一小步,我还打算选择一套UI框架来封装常见的业务,使框架更加的模块化和完善
另外值得一提的是,一段时间前已经发布了@vue/cli3,也就是vue脚手架第三个版本,看了文档发现它只是将插件配置用vue.config.js代替了,它会自动根据vue.config.js的配置生成一份webpack.config.js的文件,咱们只须要提供插件,和设置是否开启它
若是你看到这里了,那你的毅力告诉我,你之后技术确定更加厉害