VueRouter 原理实现

Vue Router 基础回顾

  • vue.use()的作用:注册插件VueRouter
  1. 参数是函数的话,vue.use()调用这个函数来注册组件
  2. 参数是对象的话,vue.use()调用这个对象的 install 方法注册组件
  • 创建路由规则 routes

  • 创建路由对象 router,将路由规则作为参数传递进来

const router =new VueRouter({
  routes
})
<!-- 导出这个路由对象 -->
export default router
  • 在 mian.js 中,这册 router 对象
new Vue({
  // 注册router对象
  router,
  store,
  render: h => h(App)
}).$mount('#app')
  • 在创建的 vue 实例中传入 router 的作用
  1. 当 vue 实例中没有 router 的时候,vue 实例没有 r o u t e 和 route和 routerouter 属性
  2. 当 vue 实例中没有 router 的时候,vue 实例中被注入了 r o u t e 和 route和 routerouter 属性
    2.1 $route:路由规则
    2.2 $router:路由对象,提供了路由的相关方法,例如 push
  3. 当我们在某些情况下不方便获取到 vue 市里的 r o u t e 时 候 , 可 以 通 过 route时候,可以通过 route,router.currentRoute 属性访问到
    在这里插入图片描述

动态路由

  • 动态路由在路由规则中,通过一个占位来匹配变化的位置
  • 组件加载的方式:懒加载
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
  • 在组件中获取动态参数的方式
{
   path: '/about/:id', //动态路由
   name: 'About',
   component: () => import(/* webpackChunkName:"about" */ '../views/About.vue'),
   props:true // 开启路由传参
 }
<template>
  <div class="about">

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

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

  </div>
</template>
<script>
export default {
  props:["id"]
};
</script>
  • 优先使用第二种传参方式,组件和路由解绑

嵌套路由

  • 关于嵌套路由默认首页的问题,可以将默认首页的 path 设置为空串("")
const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home,
    children: [
      {
        path: '', // 当加载Home页的时候默认显示About内容
        name: 'About',
        component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
      }, {
        path: 'info',
        name: 'info',
        component: () => import(/* webpackChunkName: "info" */ '../views/info.vue')
      }
    ]
  }
]

编程式导航

  • push => 记录历史
  • repalce => 替换当前历史记录
  • go 前进/后退

Hash 和 History 模式的区别

  • 两种模式都是客户端路由的实现方式,当路径发生变化的时候,不会向服务器发送请求,是由 js 监视路由的变化,然后根据不同的地址来渲染不同的内容

  • Hash 模式

    • https://music.163.com/#/playlist?id=3102961863
  • History 模式

    • https://music.163.com/playlist/3102961863

原理的区别

  • Hash 模式是基于锚点,以及 onhaschange 事件

  • History 模式是基于 HTML5 中的 History API

    • history.pushState() : 路径发生变化,不向服务器发送请求
    • history.replaceState() : 路径发生变化,不向服务器发送请求,只会改变浏览器地址栏中的地址,并将该地址添加到历史记录中,实现客户端路由
  • history.pushState()是 IE10 以后才支持,IE10 以前的浏览器只能使用 Hash 模式

  • 当路径发生变化Hash 模式和 History 都不会向服务器发送请求,但是浏览器刷新的时候,都会向服务器发送方请求(都做了静态资源管理,=>请求 url 和根目录下的静态资源一一对应)

    • Hash 模式:请求更目录下的 index.html,刷新之前的状态在 idnex.html 中通过锚点定位
    • History 模式:请求地址栏对应的静态资源(比如https://music.163.com/playlist/),因为我们我们打包的是单页面应用(index.html),找不到playlist.html页面,浏览器报错

History 模式

  • History 需要服务器的支持
  • 单页面应用,服务器只存在 index.html,不存在http://www.testurl.com/login 这样的地址会会返回 404=>找不到该页面

History 模式 -Node.js

  • 在 vue-cli 中,默认是 hash 模式,修改成 history 模式
const router = new VueRouter({
 mode: "history",
 routes
})

在这里插入图片描述

  • 在开发环境中 dev-server 做了 history 模式的处理=>当请求的页面在根目录中找不到的时候,就返回 index.html
  • 但是我们打包上线的代码所在的服务器没有处理 history 模式,需要我们手动完成该功能

Node 服务器的实现

  • 将正常的 vue-cli 创建的项目打包,生成 dist 目录
$ npm run build
  • 创建 node 服务器=>入口文件 app.js

  • 将 dist 目录下的文件拷贝到 web 目录(网站根目录)下
    在这里插入图片描述

  • 安装第三方模块 express

$ yarn add express -D
  • 因为 node 服务器默认没有对 history 模式进行处理(当请求的页面不存在时,返回根目录中的 index.html 文件),我们需要借助第三方模块来完成该工作=>connect-history-api-fallback

  • 安装 connect-history-api-fallback 模块

$ yarn add connect-history-api-fallback -D
  • 入口文件 app.js(我们先看没处理 history 模式的情形)
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")))
app.listen(3000, () => {
  console.log("服务器开启,端口:3000")
})

在这里插入图片描述

  • 切换到 about 页面,刷新
    在这里插入图片描述

  • 改建:服务器处理 history 模式

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")))
app.listen(3000, () => {
  console.log("服务器开启,端口:3000")
})

当我们在 about 页面刷新的时候,会向服务器请求 about.html 的静态资源,但是服务器根目录下没有改资源,由于 connect-history-api-fallback 模块的作用,当根目录下没有相应的静态资源的时候,返回 index.html,然后在客户端(浏览器),根据路由规则,展示 about 页面的相关内容
在这里插入图片描述

History 模式-nginx

nginx 服务器配置

  • 从官网下载 nginx 的压缩包
  • 把压缩包解压到 c 盘根目录(路径无中文),c:\nginx-1.18.0 文件夹
  • 打开命令行,切换到目录 c:\nginx-1.18.0

nginx 相关命令

// 启动-在后台启动服务器,不会阻塞当前命令行
$ start nginx 
// 重启
$ nginx -s reload
// 停止
$ nginx -s stop

在这里插入图片描述

  • 将打包好的dist目录下文件拷贝到html目录下
  • 启动nginx
$ start nginx.exe
  • 在about页面刷新,依然报错

  • 改进,修改nginx配置文件
    在这里插入图片描述

  • 重新启动服务器,刷新不报错

$ nginx.exe -s reload

Vue-router实现原理

Vue前置知识

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

Hash 模式

  • URL 中的#后面的内容作为路径地址,当#后面的内容发生变化的时候,不会像服务器发送请求,浏览器记录列表新增一条记录
  • 监听hashchange事件
  • 根据当前路由地址找到对应组件重新渲染

Hsitory 模式

  • 通过history.pushState()方法改变地址栏,该方法仅仅是改变地址栏地址,不会重新向服务器发送请求,而是在浏览器记录中新增一条记录
  • 监听popstate事件,监听浏览器历史操作的变化,在popstate事件中可以记录浏览器改变后的地址,该事件不会因为history.pushState()或者history.replaceState()而出发,只有通过调用history.forward()或者history.back()才能被触发
  • 根据当前路由地址找到对应组件重新渲染

回顾vue-Router核心代码

核心代码

在这里插入图片描述

vue-Router类图

属性
  • options
    记录vueRouter类传入的对象
  • data
  1. 响应式对象,当路由地址发生变化,对应的组件要自动更新,该对象有一个 current 属性,用来记录当前路由地址
  2. 将普通对象转换成响应式对象=>Vue.observable()
  • routeMap
    记录路由地址和组件之间的映射关系
方法
  • Constructor(options):VueRouter
  1. 返回值是一个 VueRouter 实例
  2. 初始化传入的属性和init()方法
  • static install(Vue):void 私有方法
    注册插件
  • init():void
    调用下面三个方法,把不同的代码分割到不同的方法中来实现
  • initEvent():void
    注册popState()事件,监听浏览器历史的变化window.popState()
  • createRouteMap():void
    初始化routeMap属性,把构造函数中传入的路由规则转换成键值对的形式存储到routeMap对象中,键:路由地址 值:对应的组件
  • initComponents(Vue):void
    创建router-link和router-view两个组件

VueRouter-install方法的实现

install()参数,参数一:Vue构造函数,参数二:可选,选项配置

install()的三个目标
  1. 判断当前插件(VueRouter)是否已经被安装
  2. 把Vue构造函数记录到全局变量=>因为当前的install是静态方法,在这个静态方法中我们接收了一个Vue构造函数,而将来我们在VueRouter中的一些实例方法中还要使用这个Vue的构造函数,比如我们创建router-link和router-view组件的时候,需要调用Vue.compoent()方法,所以需要将这个Vue构造函数记录到全局变量上
  3. 把创建的Vue实例时候传入的router对象注入到Vue实例上
    3.1 所有的组件都是Vue的实例
    3.2 我们在组件中使用的this.$router就是在这个时候注入到Vue实例上的
install()的三个目标的实现
  • install(Vue)中的Vue参数,实际上是Vue.use(VueRouter)传进来的Vue构造函数
    1:判断当前插件是否已经被安装
export default class VueRouter {
   
  static install(Vue) {
   
    /* 1:判断当前插件是否已经被安装:因为install是静态方法,
    也是一个对象,可以在该对象上挂载一个属性 */
    if (VueRouter.install.installed) {
   
      return
    }
    VueRouter.install.installed = true
  }
}

2.把Vue构造函数记录到全局变量

let _Vue= null  //定义一个全局变量_Vue
export default class VueRouter {
   
  static install(Vue) {
   
    if (VueRouter.install.installed) {
   
      return
    }
    VueRouter.install.installed = true
    //2.把Vue构造函数记录到全局变量
    _vue = Vue
  }
}
  1. 创建的Vue实例时候传入的router对象注入到Vue实例上
    在mian.js入口文件中
new Vue({
   
  router, //创建Vue实例的时候传入的router选项
  store,
  render: h => h(App)
}).$mount('#app')

即我们需要获取Vue构造函数的选项


let _Vue= null
export default class VueRouter {
   
  static install(Vue) {
   
    if (VueRouter.install.installed) {
   
      return
    }
    VueRouter.install.installed = true
    _Vue = Vue
    //3. 把创建的Vue实例时候传入的router对象注入到Vue实例上
    _Vue.prototype.$router= "Vue构造函数的选项"
  }
}
  • 我们知道每个Vue实例(包含组件)其实都有一个$options选项,就是new Vue()创建Vue实例的选项

let _Vue = null
export default class VueRouter {
   
  static install(Vue) {
   
    if (VueRouter.install.installed)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值