vue-router原理学习

97 阅读2分钟

「这是我参与11月更文挑战的第17天,活动详情查看:2021最后一次更文挑战」。

vue-router

动态路由 以及 嵌套路由

const routes = [
  {
    name: 'login',
    path: '/login',
    component: Login
  },
  // 嵌套路由
  {
    path: '/',
    component: Layout,
    children: [
      {
        name: 'index',
        path: '',
        component: Index
      },
      {
        name: 'detail',
        path: 'detail/:id',
        // 开启props 会把url中的参数传递给组件
        // 在组件中通过 props 来接收
        props: true,
        component: () => import('@/views/Detail.vue')
      }
    ]
  }
]



<template>
  <div>
    <!-- 方式1: 通过当前路由规则,获取数据 -->
    通过当前路由规则获取:{{ $route.params.id }}

    <br>
    <!-- 方式2:路由规则中开启 props 传参 -->
    通过开启 props 获取:{{ id }}
  </div>
</template>

<script>
export default {
  name: 'Detail',
  props: ['id']
}
</script>

<style>

</style>

编程式导航

this.$router.push("/")//path
this.$router.push({name: "Home"}) //name
this.$router.push({name: "detail",params:{id:1}}) // path: 'detail/:id',
this.$router.push({path: "detail",query:{id:1}}) 


this.$router.go(-1)  //后退上一次

this.$router.replace()  //与push 方法用法相同  但是 会替换当前的地址 不会向浏览器历史栈中添加记录

Hash与History模式区别

不管哪种模式 都是客户端路由的实现方式 当路径发生变化后,不会向服务器发送请求

  • 表现形式

    1. hash模式 music.163.com/#/playlist?…

    2. history模式 music.163.com/playlist/23…
      要用好history模式需要服务端的配合支持

  • 原理区别

    1. hash模式是基于锚点,以及onhashchange事件
    2. history模式是基于html5中的historyAPI history.pushState() IE10以后才支持 history.replaceState()

History模式的使用

在node服务器中如何配置

//导入处理history模式的模块
const history = require("connect-history-api-fallback")
//注册处理history模式的中间件
app.use(history())

在nginx中如何配置

// start nginx.exe 启动nginx
// nginx.exe -s reload 重启 nginx
// 把打包好的  放在nginx安装目录下的  html 文件夹里

// 修改nginx的配置文件 config/nginx.conf 文件
location / {
  //加一行代码   $uri 是指当前请求的这个路径  根据配置 如果找不到文件 依次往后找
  try_files $uri $uri/ /index.html   
}


VueRouter实现原理

Hash模式

  • URL中#后面的内容作为路径地址
  • 监听hashchange事件
  • 根据当前路由地址找到对应组件重新渲染

History模式

  • 通过history.pushState()方法改变地址栏
  • 监听popstate事件
  • 根据当前路由地址找到对应组件重新渲染

VueRouter类图

   VueRouter       
  • options
  • data
  • routeMap
  • Constructor(Options):VueRouter _ install(Vue):void
  • init():void
  • initEvent():void
  • createRouteMap():void
  • initComponents(Vue):void

Vue的构建版本

  • 运行时版本:不支持template模板,需要打包的时候提前编译
  • 完整版:包含运行时和编译器,体积比运行时版本大10K左右,程序运行的时候把模板转换成render函数
// 路由规则
const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue'),

    children:[
      {
        path:"/about/children",
        name:"children",
        component:() => import("@/views/children.vue")
      }
    ]
  }
]
// VueRouter
let _Vue = null;

export default class VueRouter{
    static install(Vue){
        // 1.判断当前插件是否已经被安装
        if ( VueRouter.install.installed ) {
            return; //如果插件被安装 则直接 return
        }
        // 记录当前插件已经被安装
        VueRouter.install.installed = true;
        // 2.把Vue构造函数记录到全局变量
        _Vue = Vue;
        // 3.把创建Vue实例时候传入的router对象注入到Vue实例上
        // _Vue.prototype.$router =  
        // 混入
        _Vue.mixin({
            beforeCreate(){
                if ( this.$options.router ) {
                    _Vue.prototype.$router = this.$options.router
                    this.$options.router.init()
                }
            }
        })

    }
    // 初始化三个属性
    constructor(options){
        this.options = options
        // this.routeMap = {} 
        //this.routeMap = [] //嵌套路由的实现 
        _Vue.util.defineReactive(this,'routeMap',[])
        this.current = "/"  //创建一个响应式的对象
    }
    
    // createRouteMap(){
    //     // 遍历所有的路由规则,以键值对的形式 存储到routeMap中 键是 path  值是对应的组件
    //     this.options.routes.forEach( route => {
    //         this.routeMap[route.path] = route.component
    //     })
    // }
    createRouteMap(routes){
        routes = routes || this.options.routes
        for ( const route of routes ) {
            if (route.path === "/" && this.current === "/") {
                this.routeMap.push(route)
                return
            }
            if( route.path !== "/" && this.current.indexOf(route.path) != -1 ) {
                this.routeMap.push(route)
                if (route.children) {
                    this.createRouteMap(route.children)
                }
                return
            }  
        }

    }

    initComponents(Vue){  //vue实例在全局变量中可以拿到,传递参数是为了减少这个方法和外部的依赖
        const self = this
        Vue.component("router-link",{
           props:{
               to:String
           },
        //    template:"<a :href='to' ><slot></slot></a>" 运行时版本的Vue是不支持模板编译 可以通过vue.config.js  中去配置 runtimeCompiler 设置为 true  默认值是false
         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) //三个参数 第一个data是传给popState事件的参数 第二个是title  第三个是 要改变的url
                //  将浏览器的url 赋值给 current 由于current 是响应式的数据 所以视图会发生对应的更新
                 this.$router.current = this.to
                 self.routeMap=[]
                 self.createRouteMap()
                
             }
         }
       }) 
       
       Vue.component("router-view",{
        
           render(h){
                //标记自己是一个 router-view组件
                this.$vnode.data.routerView = true
                //标记当前router-view的深度
                let depth = 0;
                let parent = this.$parent
                while(parent){
                    const vondeData = parent.$vnode && parent.$vnode.data
                    if ( vondeData ) {
                        if ( vondeData.routerView ) {
                            // 说明当前的parent是一个router-view
                            depth++;
                        }
                    }
                    parent = parent.$parent
                }
                 
                let component = null
                const route =   self.routeMap[depth]
                if (route) {
                    component = route.component
                }
                return h(component)
           }
       })
      
    }

    init () { //包装 createRouteMap   initComponents  initEvent 这三个方法 方便使用
        this.createRouteMap();
        this.initComponents(_Vue);
        this.initEvent()
    }

    initEvent(){
        window.addEventListener("popstate",() => {
            this.data.current = window.location.pathname
            this.routeMap=[]
            this.createRouteMap()
        })
    }

}