本文已参与「新人创作礼」活动,一起开启掘金创作之路
vue-router 原理
原理其实很简单,主要是vue的响应性和路由监听事件。
- 监听
postState或hashChange
history路由监听即postState事件 hash路由即hashChange事件
在路由监听回调里面会给route的current赋值
this.route.current = window.location.pathname
- 观测
route中current变化 - 更新
route-view
因为route-view用到了route参数,响应式特性会update用到的dom,在route-view中会根据route获取对应的component对象也就是组件本身,替换route-view显示
postState 和 hashChange什么时候会监听到?
postState 和 hashChange 在地址发生变化时都会触发
值得注意的 在不请求情况下点击相同的route-link postState会触发,hashChange不会。 hashChange只会在#后面的地址变化触发
另外调用history.pushState()或history.replaceState()不会触发popstate事件。
所以,a标签href 如果是#号事件会触发,如果不带#号,页面会请求,浏览器刷新的。
所以我们要重写a标签的click事件,以防止请求事件触发 #号实际上就是锚点,一般用在同页面跳到指定标题,带#的改变地址,不请求页面。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>test</title>
</head>
<body>
<div>
<a href="ceshi" onClick="tagAClick(event)">测试1</a>
<a href="#ddddd">测试2</a>
<a href="#erwe">测试3</a>
<button onclick="jumpPage()">按钮跳转</button>
</div>
<script>
window.addEventListener('hashchange', () => {
console.log('hashchange监听 \n' + 'pathname:' + window.location.pathname + ' \nhash: ' + window.location.hash)
})
window.addEventListener('popstate', () => {
console.log('popstate被监听到了 \n' + 'pathname:' + window.location.pathname + ' \nhash: ' + window.location.hash)
})
function jumpPage(e) {
history.pushState({}, '', 'button')
console.log('button点击跳转 \n' + 'pathname:' + window.location.pathname + ' \nhash: ' + window.location.hash)
}
function tagAClick(e) {
history.pushState({}, '', 'ceshi')
console.log('a标签跳转 \n' + 'pathname:' + window.location.pathname + ' \nhash: ' + window.location.hash)
e.preventDefault();
}
</script>
</body>
</html>
在线运行上述代码
点击运行结果为
点击 测试1
"a标签跳转
pathname:/cpe/boomboom/ceshi
hash: "
点击 测试2
"popstate被监听到了
pathname:/cpe/boomboom/ceshi
hash: #ddddd"
"hashchange监听
pathname:/cpe/boomboom/ceshi
hash: #ddddd"
点击 测试3
"popstate被监听到了
pathname:/cpe/boomboom/ceshi
hash: #erwe"
"hashchange监听
pathname:/cpe/boomboom/ceshi
hash: #erwe"
点击 button
"button点击跳转
pathname:/cpe/boomboom/button
hash: "
手写router-view
router-使用
完整的使用大致分为以下五个步骤
// 1. router.use 也就是调用intall方法
Vue.use(VueRouter)
// 2. 配置路由对象
const routes = [
{
path: '/',
name: 'Index',
component: Index
},
{
path: '/detail',
name: 'detail',
component: Detail
},
{
path: '/about',
name: 'about',
component: About
}
]
// 3.导出router对象,并将路由配置穿进构造方法里面
export default new VueRouter({routers})
// 4. 将router对象传进Vue实例中
new Vue({
router,
render: h => h(App)
}).$mount('#app')
// 5. 配置route-link跳转, 和component占位元素 route-view
<template>
<div class="app">
<div class="nav">
<router-link to="/">Index</router-link> |
<router-link to="/about">About</router-link> |
<router-link to="/detail">Detail</router-link>
</div>
<router-view />
</div>
</template>
根据使用来写vue-router
install方法
install方法对应的是Vue.use(VueRouter)类名直接调用所以是静态方法。
install主要作用就是让所有页面都有router对象,同时拥有route对象
let _Vue
class VueRouter {
static install(Vue) {
_Vue = Vue
Vue.mixin({
// 在mixin情况下会先调用mixin里面的生命周期方法(beforeCreate),后调用组件自身的生命周期方法(beforeCreate)
beforeCreate() {
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router
// this.$router = this.$options.router
}
}
})
}
}
export default VueRouter
构造方法
构造方法主要作用初始变量,观察route属性等
- 1.保存
options属性,保存mode属性 - 2.生成
routeMap({ url:component })对象 - 3.添加路由监听。
- 4.定义
route-viewroute-link组件
hash模式的实现
// VueRouter.js文件
let _Vue
class VueRouter {
static install(Vue) {
_Vue = Vue
Vue.mixin({
// 在mixin情况下会先调用mixin里面的生命周期方法(beforeCreate),后调用组件自身的生命周期方法(beforeCreate)
beforeCreate() {
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router
// this.$router = this.$options.router
}
}
})
}
constructor(options){
// 保存options属性
this.options = options
// 如果不传mode,默认为hash
this.mode = options.mode || 'hash'
// 生命routeMap对象
this.routeMap = {}
// 监听route.current对象
this.route = _Vue.observable({
current: '/'
})
this.init()
}
init() {
//生成routeMap
this.initRouteMap()
//添加路由监听。
this.initEvent()
// 定义组件route-view 和route-link
this.initComponent(_Vue)
}
/**
* 生成routeMap
* */
initRouteMap() {
this.options.routes.forEach(item => {
/**
* {
* path: '/about',
* name: 'about',
* component: About
* }
* */
this.routeMap[item.path] = item.component
})
}
/**
* 添加路由监听事件,在监听事件里面改变route.current的值
* */
initEvent() {
window.addEventListener('hashchange', () => {
console.log('hashchange被监听到了===========')
// hash为 #about 所以通过 slice(1) 得到 /about
that.route.current = window.location.hash.slice(1)
})
}
/**
* 定义route-view 和route-link组件
*
* route-link实际上就是<a href='path'></a>
*
* routeView 实际上就是根据不同的path展示不同的component组件
*
* 扩展:h函数
* h函数实际上就是createElement()方法
*
* //@param1 tags(标签名)、组件名称等
* //@param2 tagPropsObject 标签对应的属性数据
* //@param3 childNode 子级虚拟节点,也是需要createElement构建
* createElement(tags, tagPropsObject, childNode) 函数接受三个参数,分别是:
eg: h('div', {class: 'title'}, '我是标题') === <div class='title'>我是标题</div>
*
*
* */
initComponent(Vue) {
const that = this
Vue.component('route-link', {
props: {
to: String
},
render(h) {
return h('a',{
domprops:{
to: '#' + this.to
}
}, [this.$slots.default])
}
})
Vue.component('route-view', {
render(h){
// routeMap根据路径获取当前component
return h(that.routeMap(that.route.current))
}
})
}
}
export default VueRouter
history的实现
由于
a标签跳转的时候会请求页面,浏览器刷新,为了防止请求页面,同时实现#号的去除,我们需要重写a标签的click事件,并且阻止传播
实际开发中为防止页面刷新请求我们需要后台配合,将所有请求重定向到根页面走我们根页面判断逻辑以实现route-view替换当前component的逻辑
// VueRouter.js文件
...省略代码...
initEvent() {
//-------------------------新增代码----------------
/**
* 注意添加history路由变化popstate监听的目的是监听浏览器后退,前进事件。因为pushState不会 * 进此回调方法
*/
window.addEventListener('popstate', () => {
that.route.current = window.location.pathname
})
//-------------------------
window.addEventListener('hashchange', () => {
console.log('hashchange被监听到了===========')
// hash为 #about 所以通过 slice(1) 得到 /about
that.route.current = window.location.hash.slice(1)
})
}
// *** history模式下面防止浏览器请求页面,我们需要重新 a 标签的click事件。同时阻止事件传播。然后手动pushState到新页面 ***
initComponent(Vue){
Vue.component("router-link",{
props:{
to:String
},
render(h){
return h("a",{
attrs:{
href:this.to
},
on:{
click:this.clickhander
}
},[this.$slots.default])
},
methods:{
clickhander(e){
history.pushState({},"",this.to)
this.$router.data.current=this.to
e.preventDefault()
}
}
// template:"<a :href='to'><slot></slot><>"
})
...省略代码...
}
...省略代码...