用过Vue、React、Angular三大框架的都知道,它们主要是用来构建SPA页面(单页面应用),使用前端路由达到切换页面的效果。实际上就是切换挂载的组件,特点是局部刷新页面,而不是想传统一样刷新整个页面。路由中有两种模式,history和hash,分别是通过window.history和window.location的API完成的。
目录
1.回顾history
- history.length:返回当前页面浏览过的页面数量
- history.go(integer):接收一个整形参数(正整数、负整数均可),按照当前页面在会话中的历史记录进行移动
- history.back():返回上一页,相当于点击浏览器的回退按钮,等同于执行history.go(-1)
- history.forward():移动到下一页,相当于点击浏览器的前进按钮,等同于执行history.go(1)
- history.state:只读属性,h5新增的history的属性,表示与会话浏览历史的当前记录相关联的状态对象。
- history.pushState(data, title, ?url):在会话历史记录中添加一条记录
- history.replaceState(data, title, ?url):与pushState相似,但是不是添加一条记录,而是替换当前的记录,完成操作后,length并没有发生变化
认识一个事件onpopstate:在window对象中提供了onpopstate事件来监听历史栈的改变,只要历史栈有信息发生改变的话,就会触发该事件
2.回顾location
- location.href:获取完整的url
- location.protocol:获取当前url的协议
- location.host:获取主机名+端口号
- location.hostname::获取主机名
- location.port:获取端口号
- location.pathname:获取url的路径部分,从"/"开始,不包含参数和hash值比如:https://www.baidu.com/s/c?name="whoami",那么pathname=/s/c
- location.search:查询参数,从?开始,比如https://www.baidu.com/s/c?name="whoami",那么search=?name="whoami"
- location.hash:hash是页面中代表的唯一的片段,从#开始https://www.baidu.com/#/s/c,那么hash=#/s/c
认识一个事件onhashchange:当页面的hash值发生变化的时候就会触发该事件
3.hash和history的区别
通过hash和history的方式切换路由时都不会引起页面的刷新,他们有以下区别:
- hash模式在浏览器地址栏上有“#”作为标识,而history没有
- hash只能修改url标识部分,从#开始,而history能修好路径、参数和片段标识
- hash只有在值发生变化时才会向浏览器历史添加会话记录,而history.pushState即使地址相同也会往浏览器中添加历史会话记录
4.使用hash实现简单的前端路由
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>hash路由</title>
</head>
<body>
<ul>
<li><a href="#/">Home</a></li>
<li><a href="#/demo1">Demo1</a></li>
<li><a href="#/demo2">Demo2</a></li>
</ul>
<script type="text/javascript">
class Router {
constructor() {
this.routes = {} //用于保存hash对应的callback执行函数
this.currenthash = '' //用于保存当前的hash值
window.onhashchange = this.hashChange.bind(this) //当函数值发生变化的时候触发hashChange函数
}
//初始化
init() {
this.hashChange()
}
//当hash值发生变化时的操作
hashChange() {
this.currenthash = location.hash.slice(1) || '/'
console.log(this.currenthash)
this.routes[this.currenthash]()
}
// 添加hash和callback执行函数(添加单个路由)
route(hash, callback) {
this.routes[hash] = callback || function() {}
}
}
const router = new Router()
//注册路由
router.route('/', () => {
alert("切换到根组件")
})
router.route('/demo1', () => {
alert("切换到Demo1组件")
})
router.route('/demo2', () => {
alert("切换到Demo2组件")
})
router.init()
</script>
</body>
</html>
5.使用history实现简单的前端路由
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>history路由</title>
</head>
<body>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/demo1">Demo1</a></li>
<li><a href="/demo2">Demo2</a></li>
</ul>
<script type="text/javascript">
class Router {
constructor() {
this.routes = {} //用于保存pathname和对应擦callback函数
/*
*调用history.pushState()或者history.replaceState()不会触发popstate事件.
*popstate事件只会在浏览器某些行为下触发, 比如点击后退、前进按钮
*或者在JavaScript中调用history.back()、history.forward()、history.go()方法
*/
window.onpopstate = () => {
this.routes[this.getPathName()]()
}
}
//初始化操作
init() {
this.routes[this.getPathName()]()
}
getPathName() {
return location.pathname
}
// 注册路由函数
route(path, callback) {
this.routes[path] = callback || function() {}
}
//如果直接点击a标签,会引起页面的刷新,需要定义一个goTargetPage控制页面跳转
goTargetPage(path) {
history.pushState(null, null, path)
this.routes[path]()
}
}
const router = new Router()
//注册路由
router.route('/', () => {
alert("切换到根组件")
})
router.route('/demo1', () => {
alert("切换到Demo1组件")
})
router.route('/demo2', () => {
alert("切换到Demo2组件")
})
//初始化路由
router.init()
let routerA = document.querySelectorAll('ul > li > a')
for(let i=0;i<routerA.length;i++) {
routerA[i].onclick = function(e) {
//阻止默认行为
e.preventDefault()
//触发路由跳转函数
router.goTargetPage(e.target.getAttribute('href'))
}
}
</script>
</body>
</html>
6.Vue路由的使用
6.1下载vue-router插件
npm install vue-router -S
6.2编写使用路由3步走
- 定义路由组件
- 注册路由
- 使用路由
6.2.1定义路由组件
定义Home和About组件
<!--Home组件-->
<template>
<div>这是Home组件</div>
</template>
<script>
export default {
components: {},
props: {},
data() {
return {
}
}
};
</script>
<!--About组件-->
<template>
<div>这是about组件</div>
</template>
<script>
export default {
components: {},
props: {},
data() {
return {
}
}
};
</script>
6.2.2注册路由
新建router.js文件
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from './components/Home'
import About from './components/About'
//使用路由插件
Vue.use(VueRouter)
const routes = [
{
path: '/home',
component:Home
},
{
path: '/about',
component:About
},
]
const router = new VueRouter({
routes
})
export default router
在main.js中注册路由:
import Vue from 'vue'
import App from './App.vue'
import router from './router' //+
Vue.config.productionTip = false
let vm = new Vue({
render: h => h(App),
router //+
})
vm.$mount('#app')
6.2.3使用路由
App.vue
<template>
<div id="app">
<!-- 使用router-link控制路由跳转 -->
<router-link to="/home">Home</router-link>
<br />
<router-link to="/about">About</router-link>
<!-- 路由组件显示的位置 -->
<router-view></router-view>
</div>
</template>
<script>
export default {
}
</script>
6.3多级路由
定义Home的两个子组件,Message和News组件
<!--Message组件-->
<template>
<div>这是Message组件</div>
</template>
<script>
export default {
components: {},
props: {},
data() {
return {
}
}
};
</script>
<style scoped>
</style>
<!--News组件-->
<template>
<div>这是News组件</div>
</template>
<script>
export default {
components: {},
props: {},
data() {
return {
}
}
};
</script>
<style scoped>
</style>
修改router.js
const routes = [
{
path: '/home',
component:Home,
//+
children:[
{
path: 'message',
component:Message,
},
{
path: 'news',
component:News,
}
]
},
{
path: '/about',
component:About
},
]
修改Home.vue
<template>
<div>
<p>这是Home组件</p>
<router-link to="/home/message">Message</router-link>
<br />
<router-link to="/home/news">News</router-link>
<router-view></router-view>
</div>
</template>
<script>
export default {
components: {},
props: {},
data() {
return {
}
}
};
</script>
<style scoped>
</style>
6.4路由传参
方法一:query参数传值,query参数的格式"?k1=v1&&k2=v2"
<router-link to="/home/message?a=1&b=2">Message</router-link>
在Message中接收参数:
mounted() {
console.log(this.$route.query) //{a: '1', b: '2'}
}
方法二:params参数
这个要在router中进行配置
{
path: 'news/:id',
component:News,
}
修改Home.vue
<router-link to="/home/news/1">News</router-link>
在News组件中接收:
mounted() {
console.log(this.$route.params) // {id: '1'}
console.log(this.$route.params.id) //1
}
6.5编程式导航
相关API:
- this.$router.push(path):相当于点击路由链接(可返回)
- this.$router.replace(path):用新路由替换当前路由(不可返回)
- this.$router.back():返回上一级路由
- this.$router.go(num):以当前路由为标准,接受一个整形参数(正负均可)进行路由的前进/后退num个位置
push和replace在跳转路由时可携带query/params参数:
(1)给路由命名(编程式使用params传参时,不能使用path,只能使用name):
{
path: 'news',
name:'news', //+
component:News,
}
(2)修改Home的代码,把router-link替换成按钮,绑定事件使用编程式导航
<template>
<div>
<p>这是Home组件</p>
<button @click="goMessage">Message</button>
<br />
<button @click="goNews">News</button>
<router-view></router-view>
</div>
</template>
<script>
export default {
methods:{
goMessage() {
this.$router.push({
path:"/home/message",
query:{
a:1,
b:2
}
})
},
goNews() {
this.$router.push({
name:"news",
params:{
a:1,
b:2
}
})
}
}
};
</script>
(3)在Home和New组件中接收参数:
<!--Message组件-->
<template>
<div>这是Message组件</div>
</template>
<script>
export default {
mounted() {
console.log(this.$route.query) //{a: '1', b: '2'}
}
};
</script>
<!--News组件-->
<template>
<div>这是News组件</div>
</template>
<script>
export default {
mounted() {
console.log(this.$route.params) // {a: 1, b: 2}
}
};
</script>
<style scoped>
</style>
6.6路由守卫
vue-router提供的导航守卫主要是通过跳转或取消的方式守卫导航,简单的说,路由跳转不是一下子就完成的,和生命周期一样,分为几个阶段,每个阶段都有一个函数,这个函数能让开发者操作控制一些程序,这就是路由守卫。路由守卫分为三种:全局路由守卫、独享路由守卫、组件内路由守卫。
6.6.1全局路由守卫
所有路由配置的组件均会触发,全局路由守卫分为beforeEach()、beforeResolve()、afterEach()。
beforeEach在路由跳转前就会触发,参数包括to、from、next(参数后续讲解),主要用于等候验证;beforeResolve也是在路由跳转前就会触发,在导航被确认之前,同时在组件内守卫和异步路由组件被解析之后,解析守卫就会触发;afterEach和beforeEach相反,它是在路由跳转完成后触发,参数没有next。
//+
router.beforeEach((to, from, next) => {
/*
*进行需要的操作,比如登录验证
* */
console.log('beforeEach')
next()
})
router.beforeResolve((to, from, next) => {
/*
*进行需要的操作
* */
console.log('beforeResolve')
next()
})
router.afterEach((to, from) => {
/*
*进行需要的操作
* */
console.log('afterEach')
})
6.6.2独享路由守卫
独享是指在单个路由配置的时候设置的钩子函数,和beforeEach参数完全相同,紧跟随beforeEach执行
{
path: 'news',
name:'news',
component:News,
//+
beforeEnter: (to, from ,next) => {
/*
*进行所需要的操作
*/
console.log('beforeEnter')
next()
}
}
6.6.3组件内路由守卫
是指在组件内执行的钩子函数,相当于为组件添加生命周期钩子函数,组件内路由守卫有三个,beforeRouterEnter()、beforeRouterUpdate()、beforeRouterLeave()
- beforeRouterEnter():在渲染该组件前调用,这时候不能获取到实例的this,因为组件还没有被创建,也就是在beforeCreate之前就会执行
- beforeRouterUpdate():当前路由被改变,但是该组件被复用的时候调用,举个例子,动态路由/news/:id,在/news/1和/new/2之间跳转的时候调用
- beforeRouterLeave():导航离开该组件的时候调用
export default {
beforeRouterEnter(to, from ,next) {
/*
*进行需要的操作
*/
console.log('beforeRouterEnter')
next()
},
beforeRouterUpdate(to, from ,next) {
/*
*进行需要的操作
*/
console.log('beforeRouterUpdate')
next()
},
beforeRouterLeave(to, from ,next) {
/*
*进行需要的操作
*/
console.log('beforeRouterLeave')
next()
}
};
6.6.4路由守卫回调参数
to:目标路由对象(到哪里去)
from:即将离开的路由对象(从哪里来)
next:它是最重要的一个参数,如果没有使用它将不能进行下一步,以下是注意点:
- 但凡涉及到next参数的钩子,必须调用next才能往下执行,否则路由跳转则会停止
- 如果要中断当前导航要使用next(false)
- 可使用next(path)或者next({path:xxx})跳转到一个新的页面,传递的参数和router.push一致
- 在beforeRouterEnter钩子中next((vm) => {})内接收的回调参数为当前组件的实例vm,这个回调函数在生命周期mounted之后调用,它是所有导航守卫和生命周期最后执行的那个钩子。