目录
1 路由的基本概念与原理
路由的本质就是一种对应关系,比如说我们在url地址中输入我们要访问的url地址之后,浏览器要去请求这个url地址对应的资源。那么url地址和真实的资源之间就有一种对应的关系,就是路由。
路由分为前端路由和后端路由
1.1 后端路由:
- 概念:根据不同的用户URL请求,返回不同的内容。
- 本质:URL请求地址与服务器资源之间的对应关系。是由服务器端进行实现,并完成资源的分发
1.2 SPA (Single Page Application)
- 后端渲染(存在性能问题)
- Ajax前端渲染(前端渲染提高性能,但是不支持浏览器的前进后退操作)
- SPA(Single Page Application)单页面应用程序:整个网站只有一个页面,内容的变化通过Ajax局部更新实现、同时支持浏览器地址栏的前进和后退操作
- SPA实现原理之一:基于URL地址的hash(hash的变化会导致浏览器记录访问历史的变化、但是hash的变化不会触发新的URL请求)
- 在实现SPA过程中,最核心的技术点就是前端路由
1.3 前端路由
是依靠hash值(锚链接)的变化进行实现
-
后端路由性能相对前端路由来说较低,所以,我们接下来主要学习的是前端路由
-
概念:根据不同的事件来显示不同的页面内容,
-
本质:用户事件与事件处理函数之间的对应关系。前端路由主要做的事情就是监听事件并分发执行事件处理函数
-
简单使用:前端路由是基于hash值的变化进行实现的(比如点击页面中的菜单或者按钮改变URL的hash值,根据hash值的变化来控制组件的切换),核心实现依靠一个事件,即监听hash值变化的事件
// 监听 window 的 onhashchange 事件,根据获取到的最新的 hash 值,切换要显示的组件的名称 window.onhashchange = function() { // 通过 location.hash 获取到最新的 hash 值 }
思路:
1.在页面中有一个vue实例对象,vue实例对象中有四个组件,分别是tab栏切换需要显示的组件内容,在页面中有四个超链接。
2.当我们点击这些超链接的时候,就会改变url地址中的hash值,当hash值被改变时,就会触发onhashchange事件。在触发onhashchange事件的时候,我们根据hash值来让不同的组件进行显示
<body>
<!-- 被 vue 实例控制的 div 区域 -->
<div id="app">
<!-- 切换组件的超链接 -->
<a href="#/zhuye">主页</a>
<a href="#/keji">科技</a>
<a href="#/caijing">财经</a>
<a href="#/yule">娱乐</a>
<!-- 根据 :is 属性指定的组件名称,把对应的组件渲染到 component 标签所在的位置 -->
<!-- 可以把 component 标签当做是【组件的占位符】 -->
<component :is="comName"></component>
</div>
<script>
// #region 定义需要被切换的 4 个组件
// 主页组件
const zhuye = {
template: '<h1>主页信息</h1>'
}
// 科技组件
const keji = {
template: '<h1>科技信息</h1>'
}
// 财经组件
const caijing = {
template: '<h1>财经信息</h1>'
}
// 娱乐组件
const yule = {
template: '<h1>娱乐信息</h1>'
}
// #endregion
// #region vue 实例对象
const vm = new Vue({
el: '#app',
data: {
comName: 'zhuye'
},
// 注册私有组件
components: {
zhuye,
keji,
caijing,
yule
}
})
// #endregion
// 监听 window 的 onhashchange 事件,根据获取到的最新的 hash 值,切换要显示的组件的名称
window.onhashchange = function() {
// 通过 location.hash 获取到最新的 hash 值
console.log(location.hash);
switch(location.hash.slice(1)){
case '/zhuye':
vm.comName = 'zhuye'
break
case '/keji':
vm.comName = 'keji'
break
case '/caijing':
vm.comName = 'caijing'
break
case '/yule':
vm.comName = 'yule'
break
}
}
</script>
</body>
2 Vue-router的基本使用
2.1 Vue-router简介
- 它是一个Vue.js官方提供的路由管理器。( 官网链接)是一个功能更加强大的前端路由器,推荐使用。
- Vue Router和Vue.js非常契合,可以一起方便的实现SPA(single page web application,单页应用程序)应用程序的开发。
- Vue Router依赖于Vue,所以需要先引入Vue,再引入Vue Router
- Vue Router的特性:
- 支持H5历史模式或者hash模式
- 支持嵌套路由
- 支持路由参数
- 支持编程式路由
- 支持命名路由
- 支持路由导航守卫
- 支持路由过渡动画特效
- 支持路由懒加载
- 支持路由滚动行为
2.2 Vue Router的使用步骤(导入js)
1.导入js文件
2.添加路由链接
3.添加路由占位符(最后路由展示的组件就会在占位符的位置显示)
4.定义路由组件
5.配置路由规则并创建路由实例
6.将路由挂载到Vue实例中
1.导入js文件
<script src="lib/vue_2.5.22.js"></script>
<script src="lib/vue-router_3.0.2.js"></script>
2.添加路由链接:<router-link>是路由中提供的标签,默认会被渲染为a标签,to属性默认被渲染为href属性,
to属性的值会被渲染为#开头的hash地址
默认情况下,Vue 使用 a标签来渲染 router-link标签,可以通过router-link标签 tag 属性来指定用什么标签渲染
<router-link to="/user">User</router-link>
<router-link to="/login">Login</router-link>
3.添加路由填充位(路由占位符), 将来通过路由规则匹配到的组件,将会被渲染到router -view所在的位置。
<router-view></router-view>
4.定义路由组件
var User = { template:"<div>This is User</div>" }
var Login = { template:"<div>This is Login</div>" }
5.配置路由规则并创建路由实例***
var myRouter = new VueRouter({
//routes是路由规则数组
routes:[
//每一个路由规则都是一个对象,对象中至少包含path和component两个属性
//path表示 路由匹配的hash地址,component表示路由规则对应要展示的组件对象
{path:"/user",component:User},
{path:"/login",component:Login}
]
})
6.mian.js中:将路由挂载到Vue实例中
new Vue({
el:"#app",
//通过router属性挂载路由对象
router:myRouter
})
2.3 常用:安装和使用vue-router
- Vue路由vue-router是官方的路由插件,能够轻松的管理 SPA 项目中组件的切换。
- Vue的单页面应用是基于路由和组件的,路由用于设定访问路径,并将路径和组件映射起来
- vue-router 目前有 3.x 的版本和 4.x 的版本,vue-router 3.x 只能结合 vue2 进行使用,vue-router 4.x 只能结合 vue3 进行使用
- 安装:
npm install vue-router@4
2.3.1 整体步骤(vue-router 3.x)
第一步:安装vue-router
npm install vue-router --save
注:有可能出错,因为会安装最新的,如果搭配vue2的话用npm install vue-router@3
第二步:在模块化工程中使用它(因为是一个插件,所以可以通过Vue.use()来安装路由功能)
- 导入路由对象,并且调用Vue.use(VueRouter)
- 创建路由实例,并且传入路由映射配置
- 在Vue实例中挂载创建的路由实例
2.3.2 具体步骤:
第一步:创建路由组件
第二步:配置路由映射:组件和路径映射关系
第三步:使用路由:通过<router-link>和<router-view>
- 创建router实例:router/index.js中
//引入vue
import Vue from 'vue';
//引入vue-router
import VueRouter from 'vue-router';
import page1 from '../components/page1.vue'; //引用page1页面
import page2 from '../components/page2.vue'; //引用page2页面
//1.第三方库需要use一下才能用
Vue.use(VueRouter)
//2. 创建VueRouter对象,定义routes路由的集合,数组类型
const routes=[
{//默认跳转到page1
path: '/',
redirect: '/page1'
},
//单个路由均为对象类型,path代表的是路径,component代表组件
{path:'/page1',component:page1},
{path:"/page2",component:page2}
]
//实例化VueRouter并将routes添加进去
//默认情况下,路径的改变使用的URL的 hash,修改的话routers后添加mode: 'history'
const router=new VueRouter({
//ES6简写,等于routes:routes
routes
});
//3.抛出这个这个实例对象方便外部读取以及访问
export default router
- 挂载到Vue实例,main.js中
import Vue from 'vue'
import App from './App.vue'
//1.引用router.js
import router from './router'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
//2.一定要注入到vue的实例对象上
router
}).$mount('#app')
- 修改App.vue,使用路由
- router-link定义页面中点击触发部分,会被渲染成一个
<a>
标签。 - router-view定义页面中显示部分,会根据当前的路径,动态渲染出不同的组件。
<template>
<div id="app">
<!-- 声明路由链接 -->
<router-link to="/page1">Page1</router-link>
<router-link to="/page2">Page2</router-link>
<!-- 声明路由占位标签 -->
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'App',
components: {
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
router-link属性介绍:
- to:用于指定跳转的路径
- tag:tag可以指定
<router-link>
之后渲染成什么组件,比如我们下面的代码会被渲染成一个<li>
元素,而不是<a>
。 如:<router-link to='/home' tag='li'>
- replace:replace不会留下history记录,所以指定replace的情况下,后退键返回不能返回到上一个页面中
- active-class:当
<router-link>
对应的路由匹配成功时,会自动给当前元素设置一个router-link-active的class,设置active-class可以修改默认的名称
2.3.3 路由重定向
- 路由重定向指的是:用户在访问地址A的时候,强制用户跳转到地址C,从而展示特定的组件页面;
- 可以通过路由重定向为页面设置默认展示的组件,在路由规则中添加一条路由规则即可,如下:
var myRouter = new VueRouter({
//routes是路由规则数组
routes: [
//path设置为/表示页面最初始的地址 / ,redirect表示要被重定向的新地址,设置为一个路由即可
{ path:"/",redirect:"/user"},
{ path: "/user", component: User },
{ path: "/login", component: Login }
]
})
2.3.4 嵌套路由Children
什么是嵌套路由?
嵌套路由也称之为子路由,就是在被切换的组件中又切换其他子组件
例如:在one界面中又有两个按钮,通过这两个按钮进一步切换one中的内容
一般都是这种,子路由定义到一级路由里面
常见用处:导航栏、面包屑、侧边栏
2.3.5 路由懒加载
路由懒加载的方式
- 方式一:结合Vue的异步组件和Webpack的代码分析
const Home = resolve => { require. ensure(['../ components/Home.vue'], () => { resolve(require('../ components/Home.vue')) })};
- 方式二:AMD写法
const About = resolve => require([' ../ components/ About.vue'], resolve);
- 方式三:在ES6中,我们可以有更加简单的写法来组织Vue异步组件和Webpack的代码分割
const Home = () => import(' . ./ components/Home.vue ' )
2.4 路由跳转的两种方式
2.4.2 编程式的导航 router.push
通过调用JavaScript形式的AP|实现导航的方式,叫做编程式导航
编程式导航传递参数有两种类型:字符串、对象。
- 字符串
字符串的方式是直接将路由地址以字符串的方式来跳转,这种方式很简单但是不能传递参数:this.$router.push("home");
- 对象
想要传递参数主要就是以对象的方式来写,分为两种方式:命名路由、查询参数,下面分别说明两种方式的用法和注意事项。
- 命名路由
命名路由的前提就是在注册路由的地方需要给路由命名,命名路由传递参数需要使用params来传递,这里一定要注意使用params不是query。目标 页面接收传递参数时使用params
特别注意:命名路由这种方式传递的参数,如果在目标页面刷新是会出错的
使用方法如下:
this.$router.push({ name: 'news', params: { userId: 123 }})
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<button @click="routerTo">click here to news page</button>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data () {
return {
msg: 'Welcome to Your Vue.js App'
}
},
methods:{
routerTo(){
this.$router.push({ name: 'news', params: { userId: 123 }});
// this.$router.push('/page1');
}
}
}
</script>
接受传递的参数:
<template>
<div>
this is the news page.the transform param is {{this.$route.params.userId}}
</div>
</template>
- 查询参数
查询参数其实就是在路由地址后面带上参数和传统的url参数一致的,传递参数使用query而且必须配合path来传递参数而不能用name,目标页面接收传递的参数使用query。
注意:和name配对的是params,和path配对的是query
使用方法如下:
this.$router.push({ path: '/news', query: { userId: 123 }});
接收参数如下:
<template>
<div>
this is the news page.the transform param is {{this.$route.query.userId}}
</div>
</template>
2.4.3 声明式的导航 router-link
通过点击链接实现导航的方式,叫做声明式导航
- 字符串
<router-link to="news">click to news page</router-link>
(1)通过URL参数参数(?key=value&key-value),通过this.$route.query.key获取
在组件的生命周期中通过以下方式获取数据 this.$route.query.name this.$route.query.age
(2)通过占位符传递(路由规则中/:key/:key,路径中/value/value),通过this.$route.params.key获取
获取还是在组件的声明周期中this.$route.params.name
和this.$route.params.age
2.4.4 二者比较
- this.$router.push()
想要导航到不同的URL,使用router.push()方法,这个方法会向history栈添加一个新纪录,所以,当用户点击浏览器后退按钮时,会回到之前的URL - 当点击
<router-link>
时,这个方法会在内部调用,即点击<router-link :to="...">
等同于调用router.push(...)
- a) 声明式:
b) 编程式:router.push(…)
c) 该方法的参数可以是一个字符串路径,或者一个描述地址的对象。
// 字符串
router.push('home')
// 对象
this.$router.push({path: '/login?url=' + this.$route.path});
// 命名的路由
router.push({ name: 'user', params: { userId: 123 }})
// 带查询参数,变成/backend/order?selected=2
this.$router.push({path: '/backend/order', query: {selected: "2"}});
// 设置查询参数
this.$http.post('v1/user/select-stage', {stage: stage})
.then(({data: {code, content}}) => {
if (code === 0) {
// 对象
this.$router.push({path: '/home'});
}else if(code === 10){
// 带查询参数,变成/login?stage=stage
this.$router.push({path: '/login', query:{stage: stage}});
}
});
// 设计查询参数对象
let queryData = {};
if (this.$route.query.stage) {
queryData.stage = this.$route.query.stage;
}
if (this.$route.query.url) {
queryData.url = this.$route.query.url;
}
this.$router.push({path: '/my/profile', query: queryData});
2.4.5 动态路由
就是占位符传递参数,应用场景:通过动态路由参数的模式进行路由匹配
//路由组件 通过$route.params.id来获取路径传参的数据
var User = { template:"<div>用户:{{$route.params.id}}</div>"}
var myRouter = new VueRouter({
//routes是路由规则数组
routes: [
//通过/:参数名 的形式传递参数 ,动态路径参数以冒号开头
{ path: "/user/:id", component: User },
]
})
2.5 传递参数的方式
2.5.1 命名路由name
- 命名路由的配置规则: 为了更加方便的表示路由的路径,可以给路由规则起一个别名, 即为“命名路由’.
var myRouter = new VueRouter({
//routes是路由规则数组
routes: [
//通过name属性为路由添加一个别名
{ path: "/user/:id", component: User, name:"user"},
]
})
添加了别名之后,可以使用别名进行跳转
<router-link to="/user">User</router-link>
<router-link :to="{ name:'user' , params: {id:123} }">User</router-link>
//还可以编程式导航
myRouter.push( { name:'user' , params: {id:123} } )
最后总结:路由传递参数和传统传递参数是一样的,命名路由类似表单提交而查询就是url传递,在vue项目中基本上掌握了这两种传递参数就能应付大部分应用了,最后总结为以下两点:
1.命名路由搭配params,刷新页面参数会丢失
2.查询参数搭配query,刷新页面数据不会丢失
3.接受参数使用this.$router后面就是搭配路由的名称就能获取到参数的值
2.5.2 获取参数的两种常用方法:params和query
- params的类型:
- 配置路由格式:/router/:id
- 传递的方式:在path后面跟上对应的值
- 传递后形成的路径:/router/123,/router/abc
- query的类型:
- 配置路由格式:/router,也就是普通配置
- 传递的方式:对象中使用query的key作为传递方式
- 传递后形成的路径:/router?id= 123,/router?id=abc
首先简单说一下$router
和$route
的区别
//$router : 是路由操作对象,只写对象
//$route : 路由信息对象,只读对象
//操作 路由跳转
this.$router.push({
name:'hello',
params:{
name:'word',
age:'11'
}
})
//读取 路由参数接收
this.name = this.$route.params.name;
this.age = this.$route.params.age;
2.5.3 query与path
query传递参数 query传参要用path来引入
1)由于动态路由也是传递params的,所以在 this.$router.push() 方法中path不能和params一起使用,否则params将无效。需要用name来指定页面,即通过路由配置的name属性访问
//query传参,使用path跳转
this.$router.push({
path:'/second',
query: {
queryId:'20180822',
queryName: 'query'
}
})
//query传参接收
this.queryName = this.$route.query.queryName;
this.queryId = this.$route.query.queryId;
2.5.4 params与name
params传递参数(命名路由)
注:使用params传参只能使用name进行引入
//params传参 使用name
this.$router.push({
name:'second',
params: {
id:'20180822',
name: 'query'
}
})
//params接收参数
this.id = this.$route.params.id ;
this.name = this.$route.params.name ;
//路由
{
path: '/second/:id/:name',
name: 'second',
component: () => import('@/view/second')
}
2.5.5 总结
params是路由的一部分,必须要在路由后面添加参数名。query是拼接在url后面的参数,没有也没关系。
params一旦设置在路由,params就是路由的一部分,如果这个路由有params传参,但是在跳转的时候没有传这个参数,会导致跳转失败或者页面会没有内容。如果路由后面没有 /:id/:name,地址栏没有参数
- 传参可以使用params和query两种方式。
- 使用params传参只能用name来引入路由,即push里面只能是name:’xxxx’,不能是path:’/xxx’,因为params只能用name来引入路由,如果这里写成了path,接收参数页面会是undefined!!!。
- 使用query传参使用path来引入路由。
- params是路由的一部分,必须要在路由后面添加参数名。query是拼接在url后面的参数,没有也没关系。
- 二者还有点区别,直白的来说query相当于get请求,页面跳转的时候,可以在地址栏看到请求参数,而params相当于post请求,参数不会再地址栏中显示。
2.6 导航守卫
vue-router提供的导航守卫主要用来拦截导航,让它完成跳转或取消。
- to:Route:即将要进入的目标路由。
- from:Route:当前导航正要离开的路由。
- next:在守卫方法中如果声明了next形参,则必须调用 next() 函数,否则不允许用户访问任何一个路由
- 直接放行:next(),强制其跳转到登录页面:next(‘/login’)
这里参考el-admin里的router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Login from "@/views/Login";
import User from "@/views/system/user";
import Role from "@/views/system/role";
import {getToken} from "@/utils/auth";
Vue.use(VueRouter)
const routes = [
{
path: '/login',
name: 'Login',
component: Login
},
{
path: '/',
name: 'Dashboard',
component: () => import('@/views/Home'),
children: [
{
path: 'system/user',
name: 'User',
component: User
},
{
path: 'system/role',
name: 'Role',
component: Role
}
]
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
//假如用户A没有登录,访问了Login页面
router.beforeEach((to, from, next) => {
if (getToken() !== 'undefined' && getToken()) {//已登录
if (to.name === 'Login') next('/') // 如果访问的是登陆页面,直接跳转到主页
else next() //想去哪里就去哪里
} else if (to.name !== 'Login')
//没有登录
next({name: 'Login'}) //如果不是登陆页面,重定向到登陆页面
else next() //如果是登陆页面,就直接放行
})
export default router