vue-router是vue项目的重要组成部分,用于构建单页应用。单页应用是基于路由和组件的,路由用于设定访问路径,并将路径和组件映射起来。路由的本质就是创建url和页面之间的映射关系。html
hash模式是vue-router的默认模式。hash指的是url描点,当描点发生变化的时候,浏览器只会修改访问历史记录,不会访问服务器从新获取页面。所以能够监听描点值的变化,根据描点值渲染指定dom。vue
能够经过location.hash = "/hashpath"
的方式修改浏览器的hash值。node
能够经过监听hashchange事件监听hash值的变化。nginx
window.addEventListener('hashchange', () => { const hash = window.location.hash.substr(1) // 根据hash值渲染不一样的dom })
hash模式下,url可能为如下形式:web
http://localhost:8080/index.html#/book?bookid=1
vue-router
上面的url中既有#又有?,会让url看上去很奇怪,所以,能够使用history模式,在此模式下,url会以下面所示:express
http://localhost:8080/book/1
api
H5的history对象提供了pushState和replaceState两个方法,当调用这两个方法的时候,url会发生变化,浏览器访问历史也会发生变化,可是浏览器不会向后台发送请求。浏览器
// 第一个参数:data对象,在监听变化的事件中可以获取到 // 第二个参数:title标题 // 第三个参数:跳转地址 history.pushState({}, "", '/a')
能够经过监听popstate事件监听history变化,也就是点击浏览器的前进或者后退功能时触发。服务器
window.addEventListener("popstate", () => { const path = window.location.pathname // 根据path不一样可渲染不一样的dom })
当使用hash模式的时候,若是手动刷新浏览器,页面也可以正常显示。可是在history模式下,刷新浏览器就会出现问题。
如访问http://localhost:8080/book/1
时,服务端会查找是否有相应的html可以匹配此路径,在单页应用下,服务端只有一个index.html,因此此时匹配不到,会提示404。针对这个问题,须要服务端进行history模式支持。
在nodejs服务中,能够引入connect-history-api-fallback
插件:
const path = require('path') // 导入处理 history 模式的模块 const history = require('connect-history-api-fallback') // 导入 express const express = require('express') const app = express() // 注册处理 history 模式的中间件 app.use(history()) // 处理静态资源的中间件,网站根目录 ../web app.use(express.static(path.join(__dirname, '../web'))) // 开启服务器,端口是 3000 app.listen(3000, () => { console.log('服务器开启,端口:3000') })
在nginx服务中,能够以下方式修改配置文件,添加history模式支持:
location / { root html; index index.html index.htm; #新添加内容 #尝试读取$uri(当前请求的路径),若是读取不到读取$uri/这个文 件夹下的首页 #若是都获取不到返回根目录中的 index.html try_files $uri $uri/ /index.html; }
VueRouter核心是,经过Vue.use注册插件,在插件的install方法中获取用户配置的router对象。当浏览器地址发生变化的时候,根据router对象匹配相应路由,获取组件,并将组件渲染到视图上。
主要有三个重要点:
能够利用Vue.mixin混入声明周期函数beforeCreate,在beforeCreate函数中能够获取到Vue实例上的属性并赋值到Vue原型链上。
_Vue.mixin({ beforeCreate () { if (this.$options.router) { _Vue.prototype.$router = this.$options.router } } })
hash模式下:
history模式下:
完整版
// 存储全局使用的Vue对象 let _Vue = null class VueRouter { // vue.use要求plugin具有一个install方法 static install (Vue) { // 判断插件是否已经安装过 if (VueRouter.install.installed) { return } VueRouter.install.installed = true _Vue = Vue // 将main文件中实例化Vue对象时传入的router对象添加到Vue的原型链上。 _Vue.mixin({ beforeCreate () { if (this.$options.router) { _Vue.prototype.$router = this.$options.router } } }) } constructor (options) { this.options = options // 用于快速查找route this.routeMap = {} this.data = _Vue.observable({ current: window.location.hash.substr(1) }) this.init() } init () { this.createRouteMap() this.initComponents(_Vue) this.initEvent() } createRouteMap () { // 遍历全部的路由规则 吧路由规则解析成键值对的形式存储到routeMap中 this.options.routes.forEach(route => { this.routeMap[route.path] = route.component }) } initComponents (Vue) { // 注册router-link组件 Vue.component('router-link', { props: { to: String }, methods: { clickHandler (e) { // 修改hash location.hash = this.to // 修改current,触发视图更新 this.$router.data.current = this.to e.preventDefault() } }, render (h) { return h('a', { attrs: { href: this.to }, on: { click: this.clickHandler } }, [this.$slots.default]) } }) const that = this // 注册router-view插件 Vue.component('router-view', { render (h) { const component = that.routeMap[that.data.current] return h(component) } }) } initEvent () { // 在hash发生更改的时候,修改current属性,触发组件更新 window.addEventListener('hashchange', () => { this.data.current = window.location.hash.substr(1) }) } } export default VueRouter