Vue-Router原理以及简单实现
@vue/cli项目中的常规用法
import Vue from 'vue'
import VueRouter from 'vue-router'
// Vue.use()方法注册路由插件,参数为函数直接执行,对象调用对象上的install 方法
Vue.use(VueRouter)
import Home from '@/pages/home/index.vue'
const routes = [
{
path: '/',
component: Home,
name: 'home'
}
]
const router = new VueRouter({
routes
})
new Vue({
router
}).$mount('#app')
创建 vue 实例的过程中,传入 router 的作用:
vue 实例中注入 $route
和 $router
两个属性
$route
:路由规则,存放了路由路径,如果有参数,也可以在这里找到$router
:路由实例(路由对象),提供了跟路由有关的一些方法。
页面组件使用
<!-- 通过 router-link 创建链接 -->
<router-link>Index</router-link>
<router-link>Blog</router-link>
<router-link>Photo</router-link>
<!-- router-view 是路由组件的占位符,当匹配到对应的路径时,会替换掉这个占位符 -->
<router-view />
Hash 模式与 History 模式的区别
不管哪种模式,都是基于客户端路由的实现方式:当路径变化的时候,不会向服务端发送请求,是用 JS 监视路径变化,根据不同的地址,渲染不同的内容,如果需要服务端内容的话,发送 ajax 请求获取
-
表现形式的区别
-
- Hash 模式路径中带有个 #,# 后面的内容作为路径地址
http://127.0.0.1/#/detail?id=123
- History 模式
http://127.0.0.1/detail/123
History 模式需要服务端的配置支持
- Hash 模式路径中带有个 #,# 后面的内容作为路径地址
-
原理的区别
-
- Hash 模式基于锚点,以及 onhashchange 事件:通过锚点的值作为路由地址,当地址发生变化后,触发 onhashchange 事件,根据路径呈现页面内容
- History 模式基于 HTML5 中的 HistoryAPI
-
-
history.pushState()
【IE10以后支持】,pushState 只会改变地址栏中的地址,不会向服务器发送请求,并把地址记录到历史纪录中,从而实现客户端路由;push 改变地址栏的同时会向服务器发送请求history.replaceState()
-
History 模式的使用
- History 需要服务器的支持
- 单页应用中,如果在某个非首页的页面中刷新浏览器,服务端不存在 http://www.testurl.com/login 这样的地址会返回找不到该页面
- 在服务端应该除了静态资源外都返回单页应用中的 index.html
Nodejs 服务器部署 vue-router 的 history 模式
使用 express
+ connect-history-api-fallback
// app.js
const express = require('express')
const history = require('connect-history-api-fallback')
const path = require('path')
const app = express()
// 使用上一个同级目录 website 作为网站的根目录
app.use(express.static(path.join(__dirname, '../website')))
// 注册 history
app.use(history())
app.listen(3000, () => {
console.log('server running at port 3000')
})
Nginx 服务器部署 vue-router 的 history 模式
- 将打包结果放在 nginx 的 html 目录下
- 修改 nginx.conf 配置文件,以支持 history 模式的 vue-router
新建一个conf.d/vue-router-history.conf
文件
server {
listen 8080; # 监听端口
server_name localhost; # 服务域名
location / {
root html/website; # 网站根目录
index index.html index.htm; # index
# 尝试访问一个文件:当前浏览器请求的路径所对应的文件
# $uri 当前请求的路径,会去找当前路径请求的文件。如果找到了就直接返回这个文件
# $uri/index 如果没有找到,把它当成一个目录,然后找 index.html 或者 index.htm 文件
# /index.html 如果还是没找到,则返回网站根目录下的 index.html 文件
try_files $uri $uri/ /index.html;
}
}
- 在 nginx.conf 中引入这个配置文件
http {
# ...
# 引入自定义配置文件
include '../conf.d/*.conf';
# ...
}
简单实现Demo
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<ass></ass>
<router-link to="/">a</router-link>
<router-link to="/b">b</router-link>
<router-view></router-view>
</div>
</body>
<script type="module">
import Vue from 'https://cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.esm.browser.js'
// import VueRouter from './router.js'
let _Vue = null
class VueRouter {
// Vue.use(注册插件)参数如果是对象则调用install方法,如果为函数执行函数
static install (Vue) {
// 如果已经安装直接返回
if(VueRouter.install.installed) return;
// 安装标识挂载到install上
VueRouter.install.installed = true
// 把Vue构造函数挂载到全局
_Vue = Vue
// 通过mixin在每个vue实例创建之前注入router对象
_Vue.mixin({
beforeCreate() {
// this是Vue构造函数
// this.$options.router 是经过 VueRouter 的实例。
if(this.$options.router) {
console.log(this)
_Vue.prototype.$router = this.$options.router
}
}
})
}
// 构造函数
constructor(options) {
this.options = options
// 解析options中routes路由配置 键值对path->component
this.routerMap = {}
// console.log(_Vue.observable)
// 响应式的对象
// 保证页面可以通过路由变化响应不同的页面
// 让一个对象可响应: https://cn.vuejs.org/v2/api/#Vue-observable
this.data = _Vue.observable({
current: '/'
})
// 包装方法 初始化
this.init()
}
init() {
this.createRouteMap()
this.initComponent(_Vue)
this.initEvent()
}
// 创建路由map 键值对
createRouteMap() {
this.options.routes.forEach(route => {
this.routerMap[route.path] = route.component
})
}
// 初始化router-view和router-link两个组件
initComponent(Vue) {
Vue.component("router-link", {
props: {
to: String
},
// template: '<a :href="to"><slot></slot></a>'
// 可以直接写 render 方法
render(h) {
return h("a", {
attrs: {
href: this.to
},
on: {
click: this.clickhander
}
}, [this.$slots.default])
},
methods: {
clickhander(e) {
// pushState 3个参数:data数据 title网页标题 url地址
// data 对象,将来触发 popState 时,传递给 popState 的事件对象。
// 这里用不上,所以传递空对象
history.pushState({}, "", this.to)
// 修改current值同时因为current可观测 所以切换到对应组件
this.$router.data.current = this.to
// 取消超链接的默认事件
e.preventDefault()
}
}
})
// 这里的this是vueRouter实例
const self = this
console.log(this)
Vue.component("router-view", {
render(h) {
// 当前路由组件
const cm = self.routerMap[self.data.current]
return h(cm)
}
})
}
// 解决点击浏览器历史记录的前进后退找不到页面
initEvent() {
window.addEventListener("popstate", () => {
console.log(window.location)
this.data.current = window.location.pathname
})
}
}
Vue.use(VueRouter)
const Ass = {template: '<div>aaaaaa</div>'}
const Bss = {template: '<div>bbbbbbb</div>'}
const routes = [{
path: '/',
component: Ass
}, {
path: '/b',
component: Bss
}]
const router = new VueRouter({
routes
})
console.log(router)
Vue.component("Ass",Ass)
Vue.component("Bss",Bss)
const app = new Vue({
router
}).$mount('#app')
</script>
</html>