分析vue router实现原理之前 ,先来简单过一遍vue router的基本使用
使用步骤
1、首先通过@vue/cli创建vue的基础项目 并选中(Router)
2、在router/index.js文件中
import Vue from 'vue'
import VueRouter from 'vue-router'
//1、注册路由插件
//通过vue.use()方法用来注册插件用,它会调用传入对象的install方法,
//如果传入的是一个函数会直接调用这个函数,后面在具体分析vue.use源码是怎么实现的
Vue.use(VueRouter)
//2、路由规则
const routes=[
{
path:'/',
name:'home',
component:Home
}
]
// 3、创建路由对象
const router = new VueRouter({
routes
})
export default router
3、注册路由对象
main.js中
import Vue from 'vue'
import router from './router'
new Vue({
router,
render:h=>h(App)
}).$mount(#app)
4、router-view使用
在app.vue中
<template>
<!-- 4、创建路由组件的占位-->
<router-view/>
</template>
当地址栏中路径发生变化会在router-view位置渲染对应的组件
动态路由
const routes=[
{
path:'/detail/:id',
name:'detail',
//方式1:开启props,会把url中的参数传递给组件
//在组件中通过props接收参数(优点是不依赖于路由规则)
//方式2:不开启props,组件中通过 $route.params.id获取参数
props:true,
//路由懒加载,提高程序性能
component:()=>import ('../views/detail.vue')
}
]
嵌套路由
会将外部的layout的path和children的path进行合并,首页的路径可以写成'',加载的时候会先加载layout组件在加载children的组件
const routes=[
{
path:'/',
component:layout,
children:[
{
path:'',
name:index,
component:Index,
},
{
path:'detail/:id',
name:detail,
props:true,
component:()=>import ('../views/detail.vue')
}
]
}
]
编程式导航
//push方法会记录历史
//字符串方式
this.$router.push('/')
//对象方式
this.$router.push({name:'detail',params:{id:1}})
//replace不会记录历史
this.$router.replace('/login')
//go -1返回上一个页面
this.$router.go(-1)
hash和history模式区别
hash模式是基于锚点,和onhashchange事件
history模式是基于H5中的history API
- history.pushState() IE10以后才支持
- push方法时候路径会方式变化,会向服务器发送请求,history.pushState()会改变浏览器的地址栏的地址但是不会向服务器发送请求
- history.replaceState()
- replaceState() 是修改了当前的历史记录项而不是新建一个
history模式
- history模式需要服务器的支持
- 单页应用中,服务器不存在http://10.12.22.33/login这样的地址就会返回找不到该页面
- 服务端应该除了返回静态资源外都返回单页应用中的index.html
在服务端如何配置?
1.node服务器
需要导入处理history的插件
2.nginx服务
修改配置文件
http{
server{
location /{
root html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
# 尝试访问当前访问路径 没有的话访问当前路径下的index.html 找不到访问html/index.html
}
}
}
实现原理
hash模式
- url中#后面的内容作为路径地址
- 监听hashchange事件
- 根据当前地址找到对应的组件并渲染
history模式
- history.pushState()方法改变地址栏
- 监听popstate事件
- 根据路径地址找对应的组件渲染
模拟vue router
实现思路
- 创建 VueRouter 插件,静态方法 install
- 判断插件是否已经被加载
- 当 Vue 加载的时候把传入的 router 对象挂载到 Vue 实例上(注意:只执行一次)
- 创建 VueRouter 类
- 初始化,options、routeMap、data(简化操作,创建 Vue 实例作为响应式数据记录当前路径)
- initRouteMap() 遍历所有路由信息,把组件和路由的映射记录到 routeMap 对象中
- 注册 popstate 事件,当路由地址发生变化,重新记录当前的路径
- 创建 router-link 和 router-view 组件
- 当路径改变的时候通过当前路径在 routerMap 对象中找到对应的组件,渲染 router-view
模拟代码
-
创建VueRouter插件 ```vue let _Vue =null export default class VueRouter{ static install(Vue){ //如果插件已经安装直接返回 if(VueRouter.install.installed && Vue===_Vue) return
VueRouter.install.installed = true _Vue = Vue Vue.mixin({ beforeCreate(){ //判断 router 对象是否已经挂载了 Vue 实例上 if(this.$options.router){ //把vue实例传入的router注册到vue实例 _Vue.prototype.$router=this.$options.router } } })
} } ```
-
实现VueRouter类 构造函数
constructor(options){ this.options = options //记录路径及对应的组件 this.routeMap = {} this.data = _Vue.observable({ current: window.location.pathname //"/" }); }
-
实现 VueRouter 类 - initRouteMap()
initRouteMap(){ //遍历所有的路由信息,记录路径和组件的映射 this.options.routes.forEach(route=>{ this.routeMap[route.path]=route.component }) }
-
实现 VueRouter 类 - 注册事件
```vue
initEvents(){
//当路径变化之后,重新获取当前路径并记录到 current
window.addEventListener("popstate", () => {
this.data.current = window.location.pathname;
});
}
```
-
实现vuerouter类 - router-link 和router-view组件 ```vue initComponents(Vue) { const self = this; Vue.component("router-link", { props: { to: String }, //运行时不支持template模版 需要vue.config.js 设置 runtimeCompiler:true // template: '' render(h) { return h( "a", { attrs: { href: this.to }, on: { click: this.clickHandler } }, [this.slots.default] ); }, methods: { clickHandler(e) { e.preventDefault(); history.pushState({}, "", this.to); this.router.data.current = this.to; } } });
Vue.component("router-view", { render(h) { //根据当前路径找到对应的组件,注意 this 的问题 let cm = self.routerMap[self.data.current]; if (!cm) { cm = self.routerMap["*"]; } return h(cm); } }); } ```
-
实现init
init () { this.initRouteMap() this.initEvents() this.initComponents(_Vue) } // 插件的 install() 方法中调用 init() 初始化 if (this.$options.router{ _Vue.prototype.$router=this.$options.router // 初始化插件的时候,调用 init this.$options.router.init() }