文章说明:本文章为拉钩大前端训练营所做笔记和心得,若有不当之处,还望各位指出与教导,谢谢 !
Vue-Router
一、Hash模式和History模式
Hash和History模式区别
不管哪种模式都是客户端路由的实现方式,也就是当路由的路径发生变化之后,不会向服务器发送请求,是由js接收路径的变化然后根据不同的地址渲染不同的内容。如果需要服务器端内容的话会发送ajax请求来获取。
表现形式的区别:
-
Hash 模式
https://music.163.com/#/playlist?id=3102961863 -
History 模式
https://music.163.com/playlist/3102961863,要用好这个模式需要服务端配置支持
原理的区别:
- Hash 模式是基于锚点,以及 onhashchange 事件;通过 锚点的值 作为路由地址,当地址发生变化后,触发 onhashchange 事件,在onhashchange这个事件中我们记录当前路由地址,并找到该路径对应的组件然后重新渲染。即根据路径决定页面中呈现的内容。
- History 模式是基于 HTML5中的 History API,要想兼容ie9以上的浏览器必须使用hash模式。
1.history.pushState() IE10 以后才支持
当调用 history.push() 时,路径会发生变化,要向服务器发生请求;
当调用 history.pushState() 时,不会向服务器发生请求,只会改变浏览器路径栏中的地址,并且将地址记录到历史记录中。也就是说,可以使用 pushState() 实现客户端路由。但是,需要在 IE10 以后使用。
2.history.replaceState()
History模式的使用
- 单页应用中,服务端不存在 http://www.testurl.com/login 这样的地址,会返回找不到该页面
- 在服务端应该除了静态资源外都返回单页应用的 index.html
nginx 下的history
1.在官网下载nginx的压缩包
2.把压缩包解压到硬盘根目录:D:\nginx-1.17.10,目录中不能出现中文
3.打开命令行,切换到D:\nginx-1.17.10
4.启动nginx
$ start nginx
5.打开nignx目录里的conf配置文件
6.修改完成之后重启:
nginx -s reload
7.页面输入访问localhost,即可访问,解决了vue-router中history模式的问题。
VueRouter实现原理
VueRouter是前端路由,当路径切换的时候在浏览器端判断当前路径并加载当前路径对应的组件。
hash模式:
- URL 中 # 后面的内容作为路径地址
- 监听 hashchange 事件
- 根据当前路由地址找到对应组件重新渲染
History 模式
- 通过 history.pushState() 方法改变浏览器的地址栏,仅仅是改变地址栏,并把当前地址记录到浏览历史中,并不会真正跳转到指定的路径,也就是浏览器不会像服务器发送请求
- 监听 popstate 事件,可以监听到浏览器历史操作的变化,在popstate处理函数中记录改变后的地址,点击浏览器的前进或者后退的按钮的时候,或者调用history.back(),forward()方法才会触发popatate事件
- 当地址改变之后,根据当前路由地址找到对应组件重新渲染
VueRouter类图:从上至下为类名,属性,方法。_ 是静态的方法,+ 是对外公开的方法。
options:记录构造函数中传入的对象。
routeMap:用来记录路由地址和组件的对应关系的对象,把路由规则解析到routeMap里面
data:对象,里面的current属性记录当前路由地址的。设置这个data的目的是需要响应式的对象,路由地址发生变化之后,对应的组件要自动更新。
项目目录:
大概有四步,先看看官方的Vue-router使用(router/index.js):
main.js中:
实现history模式下的vuerouter,第一步:
建立一个VueRouter文件夹,然后新建文件index.js,创建实例VueRouter,Vue是使用use(plugins)方法将插件注入到Vue中,use方法会自动检测注入插件VueRouter内的install方法,如果有,则执行install方法。VueRouter,Vuex包括一些组件都是通过插件的方式来实现的。如果是在浏览器环境,在index.js内则会自动调用use方法。如果是基于node环境,需要手动调用。
_install方法:
其中要对Vue实例混入beforeCreate钩子操作(在Vue的生命周期阶段会被调用)
// 定义一个全局变量
let _Vue = null
export default class VueRouter {
static install (Vue) {
// 1.判断当前插件是否已被安装
// 给install方法增加一个installed属性
if (VueRouter.install.installed) {
return
}
// 记录插件被安装
VueRouter.install.installed = true
// 2.把Vue构造函数记录到全局变量(我们要在VueRouter的实例方法中类使用Vue的构造函数,比如创建组件的时候使用Vue.component)
_Vue = Vue
// 混入,给所有的Vue实例设置选项
_Vue.mixin({
beforeCreate () {
if (this.$options.router) {
// 3.把创建Vue实例时候传入的router对象注入到所有Vue实例上
_Vue.prototype.$router = this.$options.router// 获取到Vue实例才能写
// 当插件注册完成的时候调用初始化方法
this.$options.router.init()
}
}
})
}
}
第二步:
创建VueRouter中的初始化方法constructor
// 初始化三个属性options,routeMap,data属性
constructor (options) {
// 记录传入的options
this.options = options
// 解析路由规则存储到routeMap里面
// router-view这个组件会根据当前地址寻找routeMap中对应的组件,渲染到浏览器中
this.routeMap = {
}
this.data = _Vue.observable({
// 存储当前的路由地址,当路由变化时要自动加载组件
// data要设置成响应式的对象,observable用来创建响应式对象,可以直接用着渲染函数或者计算属性里面
current: '/' // 当前的路由地址,默认为 /
})
}
第三步:
先来实现createRouteMap()方法,该方法把构造函数中传进来的选项中的路由规则(routes)转换为键值对的形式存储到routeMap里面,键为路由地址,值为地址对应的组件。地址变化,需要找到对应的组件并渲染到浏览器上。
createRouterMap () {
// 遍历所有路由规则,然后解析成键值对的形式存储的routeMap这个对象里面来
// 所有的路由规则都在options这个选项中,构造函数中传入了一个options,里面的某个属性叫routes
// routes 这个数组存储了所有的路由规则,在构造函数中我们把options这个选项存储到了当前VueRouter的options属性里面
// 当把routes 所有内容解析出来放到routerMap里面
this.options.routes.forEach(route => {
this.routeMap[route.path] = route.component
})
}
第四步:
实现initComponents方法,这个方法里面要创建router-link和router-view组件。
app.vue:
<template>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link> |
<router-link to="/video">Video</router-link>
</div>
<router-view/>
</div>
</template>
router-link需要接受字符串类型参数to,也就是超链接的地址,把home这个内容要渲染到a标签里面.
router-view这个组件相当于一个占位符,在router-view组件内部要根据当前的路由地址获取到当前的路由组件去渲染到router-view的位置上
initComponents (Vue) {
// 传入Vue而不传入_Vue是为了减少该方法和外部的依赖
Vue.component('router-link', {
// 接受外部传入的参数
props: {
to: String
},
// 使用运行时版本的vue也就是不带编译器,此时不支持template选项
// 编译器的作用只是把template转换为render函数
// template:'<a :href = "to><slot></slot></a>'
// 在运行时版本中直接写render函数
render (h) {
return h('a', {
attrs: {
href: this.to
},
on: {
click: this.clickHandler// 给click注册clickHandler的点击事件
}
}, [this.$slots.default])
// 要创建的元素对应的选择器;设置a标签属性;通过代码(获取默认插槽:this.$slots.default)的形式获取slot内容放到数组当中来。
},
methods: {
clickHandler (e) {
// 传入事件参数e
// 调用pushstate方法改变浏览器的地址栏,不会向服务器发送请求
history.pushState({
}, '', this.to)
this.$router.data.current = this.to
e.preventDefault()// 阻止e的默认行为
}
}
// template: '<a :href = "to"><slot></slot></a>'
})
// 定义变量存储this,initcomponent中的this存储到self里面,self就是VueRouter的实例
const self = this
Vue.component('router-view', {
render (h) {
// 先找到当前路由的地址,再根据当前路由的地址去routermap对象中来找路由地址对应的组件
// 然后调用h函数帮我们把这个找到的组件转换成虚拟dom直接返回
// h函数还能直接把一个组件转换成虚拟dom
const component = self.routeMap[self.data.current]
return h(component)
}
})
}
创建init方法包含住createRouterMap()和initComponents(),以及后续的initEvent
init () {
this.createRouterMap()
this.initComponents(_Vue)
this.initEvent()
}
第五步:
实现initEvent(),这是个初始化方法,需要放到init()里面。
点击浏览器的前进和后退方法后,地址栏虽然发生变化,但是页面里的内容却为改变,也就是并没有加载地址对应的组件。我们需要当浏览器地址发生变化后,页面要加载地址对应的组件实现更新,此时需要popstate事件,这个事件会在当浏览器历史发生变化时触发。
initEvent () {
// 注册popstate事件
window.addEventListener('popstate', () => {
// 把当前地址栏的地址取出来,把路径部分取出放到data.current里
this.data.current = window.location.pathname
})
}