手写Vue-router核心原理

内容输出来源 拉勾教育大前端高薪训练营

一、基本使用

import Vue from 'vue'
import VueRouter from 'vue-router' // 路由组件
import index from '@/views/index' // 组成插件
Vue.use(VueRouter)
// 路由规则
const routes = [
  {
    name: 'index',
    path: '/',
    component: index
} ]
// 路由对象
const router = new VueRouter({
  routes
})
export default router

二、Vue 动态路由


import Vue from 'vue'
import Index from '../views/Index.vue'
const routers = [
  {
    path: '/',
    name: 'Index,
    component: Index
  },
  {
    path: '/detail/:id',
    name: 'Detail',
    // 开启props, 会把URL中的参数传递给组件
    // 在组件中通过props来接收参数
    props: true,
    component: () => import('../views/Detail.vue')
  }
]
// 获取参数
// 1 $route.params.id
// 2 通过开启props 获取id 
export default {
  name: 'Detail',
  props: ['id']
}

三、Vue 嵌套路由 Vue Router

{
  path: '/',
  component: Layout,
  children: [
    {
      name: 'index',
      path: '',
      component: 'Index',
    },
    {
      name: 'detail',
      path: 'detail:id',
      props: true,
      component: () => import('@/views/Detail.vue')
    }
  ]
}

四、编程式导航

<button @click="push">push</button>
<button @click="go">go</button>

export default {
  name: 'Login',
  methods: {
    push() {
      this.$router.push('/')
      // 对象 路由名称
      this.$router.push({name: 'Home'})
    },
    go() {
      this.$router.go(-1)
    }
  }
}

五、Hash模式和History 模式的区别

  • Hash模式
  • https://music.163.com/#/playList?id=123456
  • History 模式 需要服务端配和支持
  • https://music.163.com/playList/123456

原理的区别

  • Hash 模式是基于锚点,以及onhashchange事件
  • History 模式是基于HTML5中的History API
    history.pushState() IE10后才支持
    history.replaceState()

History 模式的使用

  • History 需要服务器的支持
  • 单页应用中,服务端不存在https:www.testurl.com/login这样的地址会返回找不到页面
  • 在服务端应该除了静态资源外都返回单页面应用中的index.html
{
  path: '/',
  component: Layout,
  children: [
    {
      name: 'index',
      path: '',
      component: 'Index',
    },
    {
      name: 'detail',
      path: 'detail:id',
      props: true,
      component: () => import('@/views/Detail.vue')
    },
    {
      name: '404',
      path: '*',
      props: true,
      component: () => import('@/views/404.vue')
    },
  ]
}

const router = new VueRouter({
  mode: 'history',
  routers
})
export default router

六、History 模式- node.js

const path = require('path')
// 导入处理history 模式的模块
history = require('connect-history-api-fallback')
// 导入express
const express = require('express')

consty app = express()
// 注册处理history 模式的中间价
app.use(history)
// 处理静态资源的中间件,网站根目录 ../web
// 开启服务器,端口是3000
app.use(express.static(path.join(__dirname), '../web'))

app.listen(3000, () => {
  console.log('服务器开启,端口3000')
})
// 在刷新的时候如果服务器没有找到页面,会默认把单页面应用的index.html返回浏览器
// 浏览器在加载的过程中会判断是否存在, 存在则跳转对应的页面

七、History nginx

  • nginx 服务器配置
  • 从官网下载nginx的压缩包
  • 把压缩包解压C盘根目录 c:nginx-1.18文件夹
  • 打开命令行- 切换到根目录c:nginx-1.18.0
/*
* start nginx 启动
* nginx -s reload 重启
* nginx -s stop 停止
*/

// 刷新页面 服务器返回404页面
// 解决
nginx.conf 配置 try_files $uri/ /index.html
// 重启nginx

server {
  listen    80;
  server_name localhost;

  location / {
    root html;
    index index.html index.htm;
    try_files $uri/ /index.html;
  }
  error_page 500 502 503 504 /50x.html
}

八、Vue Router实现原理

History 模式

  • 通过histoty.pushState()方法改变地址栏
  • 监听popstate 时间
  • 根据当前路由地址找到组件重新渲染页面

前置知识

  • 插件
  • 混入
  • Vue.observable()
  • 插槽
  • render函数
  • 运行时和完整版的Vue

VueRouter 模拟实现 - 分析核心代码

// router/index.js
// 注册插件
Vue.use(VueRouter)
// 创建路由对象
const router = new VueRouter({
  routers: [
    {
      name: 'Login',
      path: '/',
      component: () => import('@/views/Login.vue')
    }
  ]
})
// main.js
// 创建vue 实例, 注册router对象

new Vue({
  router,
  render: h => h(App)
}).$mount('#app)
  • 创建 VueRouter 插件,静态方法 install
    • 判断插件是否已经被加载
  • 当 Vue 加载的时候把传入的 router 对象挂载到 Vue 实例上(注意:只执行一次) 创建 VueRouter 类
  • 初始化,options、routeMap、app(简化操作,创建 Vue 实例作为响应式数据记录当前路 径)
  • nitRouteMap() 遍历所有路由信息,把组件和路由的映射记录到 routeMap 对象中
  • 注册 popstate 事件,当路由地址发生变化,重新记录当前的路径
  • 创建 router-link 和 router-view 组件
  • 当路径改变的时候通过当前路径在 routerMap 对象中找到对应的组件,渲染 router-view

vue Router 类图

  • options
  • data
  • routerMap
  • Constructor(options): VueRouter
  • _install(Vue): void
  • init():void
  • initEvent():void
  • createRouterMap(): void
  • initComponents(Vue): void

九、install

创建一个文件夹vueRouter/index.js

// 首先来实现intsall方法
let _Vue = null

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

十、Vue-router构造函数

 constructor (options) {
   this.options = options
   this.routeMap = {}
   this.data = _Vue.observable({
     current: '/'
   })
 }

十一、Vue-router createRouteMap

作用

  • 遍历所有的路由规则, 将路由规则解析成键值对形式存储在createRouterMap这个对象上,
  • 所有的路由规则都在options
  createRouteMap () {
    // 遍历所有的路由规则, 将路由规则解析成键值对形式存储在createRouterMap这个对象上,
    // 所有的路由规则都在options 上
    this.options.routes.forEach(route => {
      this.routeMap[route.path] = route.component
    })
  }

十二、Vue-router router-link

 init () {
   this.createRouteMap()
   this.initComponents(_Vue)
 }

initComponents (Vue) {
   // router-link 接收一个字符串参数to,也就是超链接的地址,
   // routerlink最终渲染成超链接的内容在routerlink标签之间
   Vue.component('router-link', {
     props: {
       to: String
     },
     template: '<a :href="to"><slot></slot></a>'
   })
 }
 
// 在install中调用
this.$options.router.init()

修改router/index.js的vue官方router

// 使用刚刚自己写的
import VueRouter from '../vueRouter'

打开浏览器会看到什么都没有,出现两个错误
在这里插入图片描述

  • 第一个是告诉我们你正在使用运行时版本的Vue,模版编辑器不可用,可以使用预编译把模版编译成render函数,或者使用包含编译器的版本的Vue
  • 第二个是告诉我们没有注册router-view这个组件

Vue的构建版本

  • 运行时版本: 不支持template 模版,需要打包的时候提前编译
  • 完整版本: 包含运行时和编译器,体积比运行时大10k左右,程序运行的时候把模版转换成render函数

十四、完整版本的Vue

我们可以在根目录下创建vue.config.js 文件进行配置解决这个问题

module.exports = {
  runtimeCompiler: true
}

重新编译即可看到错误消失
在这里插入图片描述

十五、 VueRouter render

不使用vue.config.js来解决,将vue.config.js删除,重新编译

 initComponents (Vue) {
   // router-link 接收一个字符串参数to,也就是超链接的地址,
   // routerlink最终渲染成超链接的内容在routerlink标签之间
   Vue.component('router-link', {
     props: {
       to: String
     },
     render (h) {
       return h('a', {
         attrs: {
           href: this.to
         },
       }, [this.$slots.default])
     },
     // template: '<a :href="to"><slot></slot></a>'
   })
 }

十六、 Vue-router router-view

initComponents (Vue) {
  // router-link 接收一个字符串参数to,也就是超链接的地址,
  // routerlink最终渲染成超链接的内容在routerlink标签之间
  Vue.component('router-link', {
    props: {
      to: String
    },
    render (h) {
      return h('a', {
        attrs: {
          href: this.to
        },
        on: {
          click: this.clickHandler
        }
      }, [this.$slots.default])
    },
    methods: {
      clickHandler (e) {
        history.pushState({}, '', this.to)
        this.$router.data.current = this.to
        e.preventDefault()
      }
    }
    // template: '<a :href="to"><slot></slot></a>'
  })
  const self = this
  Vue.component('router-view', {
    render (h) {
      const component = self.routeMap[self.data.current]
      return h(component)
    }
  })
}

此时点击路由切换正常跳转
在这里插入图片描述

Vue-router initEvent

当我们点击浏览器的前进和后退按钮时,地址栏发生了变化,但是组件并没有变化,也就是说当我能点击前进或者后退时,我们并没有重新加载这个组件

initEvent () {
   window.addEventListener('popstate', () => {
     this.data.current = window.location.pathname
   })
 }

好啦,到这里完整的vue-router就写完啦~

完成代码请点击这里

猜你喜欢

转载自blog.csdn.net/sinat_35349493/article/details/107753383