• 前端路由与vue-router原理


    前端路由vue-router

    前端路由概念

    理解:url与ui的映射关系,再改变url的情况下,展示对应内容,但是不会对整个页面进行刷新

    (1)监视地址栏变化;(2)查找当前路径对应的页面组件;(3)将找到的页面组件替换router-vieW 的位置

    实现前端路由的两个核心:

    1》修改url,但是页面不刷新

    2》能够检测到url的变化

    hash

    1、hash特点

    hash 是 URL 中 hash (#) 及后面的那部分,常用作锚点在页面内进行导航,改变 URL 中的 hash 部分不会引起页面刷新

    hash 的变化不会导致浏览器向服务端发送请求,所以也就不会刷新页面;hash的实现全部在前端,不需要后端服务器配合,兼容性好,主要是通过监听hashchange事件,处理前端业务逻辑

    2、hashchange事件

    通过hashchange事件可以监听到hash值的变化

    修改hash的几种方式:

    • 通过浏览器的前进、后退(调用history的back,go,forward方法触发该事件)
    • 通过a标签的href属性修改url
    • 通过window.location修改url
    history

    1、pushState与replaceState

    HTML5新出来的 history.pushState() 和 history.replaceState() 能够改变 URL 的 path 部分但是不会引起页面刷新

    仅改变网址url,网页不会真的跳转,也不会获取到新的内容,本质上网页还停留在原页面

    基本语法:

    window.history.pushState(state, title, targetURL);
    @状态对象:传给目标路由的信息,可为空
    @页面标题:目前所有浏览器都不支持,填空字符串即可
    @可选url:目标url,不会检查url是否存在,且不能跨域。如不传该项,即给当前url添加data
    
    • 1
    • 2
    • 3
    • 4
    window.history.replaceState(state, title, targetURL);
    @类似于pushState,但是会直接替换掉当前url,而不会在history中留下记录
    
    • 1
    • 2

    2、popstate事件

    popstate事件会在点击浏览器的前进、后退按钮(调用back、forward、go方法)时触发;

    通过pushState、replaceState或标签改变 URL 不会触发 popstate 事件

    
    
      
        
        
        history
      
      
        跳转
        
        
      
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    但是我们可以拦截pushState、replaceState的调用和a标签的点击事件来检测url的变化

    如果要触发popstate可以通过js 调用history的back,go,forward方法来触发该事件

    原生js实现前端路由

    基于hash
    
    
      
        
        
        hash
      
      
        修改url
        
    基于哈希来实现前端路由
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    基于history
    
    
      
        
        
        history
      
      
        history
        
        
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    我们通过a标签的href属性来改变URL的path值(当改变path值时,如果不是只修改hash,默认会触发页面的跳转,虽然不触发popstate事件,但是真个页面回刷新,所以需要拦截 标签点击事件默认行为)点击时使用 pushState 修改 URL并更新手动 UI,从而实现点击链接更新 URL 和 UI 的效果。

    我们监听popState事件。一旦事件触发,就改变routerView的内容。

    vue-router路由

    三种模式:

    1. hash:哈希模式

    2. history:历史模式

    3. abstract:抽象模式

    hash模式和history模式都是利用的浏览器api,abstract是在不支持浏览器API的环境下使用

    基本使用

    import VueRouter from 'vue-router'
    Vue.use(VueRouter)
    const router = new VueRouter({
      mode: 'hash',
      routes:[],
    })
    export default router
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    从它的使用上可以看出,VueRouter是一个类或构造函数,它通过use来注册,那肯定有一个install方法

    下面是install的源码:“vue-router”: “^3.6.5”

    import View from './components/view'
    import Link from './components/link'
    
    export let _Vue
    
    export function install (Vue) {
      if (install.installed && _Vue === Vue) return
      install.installed = true
    
      _Vue = Vue
    
      const isDef = v => v !== undefined
    
      const registerInstance = (vm, callVal) => {
        let i = vm.$options._parentVnode
        if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
          i(vm, callVal)
        }
      }
    
      Vue.mixin({
        beforeCreate () {
          if (isDef(this.$options.router)) {
            this._routerRoot = this
            this._router = this.$options.router
            this._router.init(this)
            Vue.util.defineReactive(this, '_route', this._router.history.current)
          } else {
            this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
          }
          registerInstance(this, this)
        },
        destroyed () {
          registerInstance(this)
        }
      })
    
      Object.defineProperty(Vue.prototype, '$router', {
        get () { return this._routerRoot._router }
      })
    
      Object.defineProperty(Vue.prototype, '$route', {
        get () { return this._routerRoot._route }
      })
    
      Vue.component('RouterView', View)
      Vue.component('RouterLink', Link)
    
      const strats = Vue.config.optionMergeStrategies
      // use the same hook merging strategy for route hooks
      strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    注册了RouterView、RouterLink两个全局组件

    mixin的作用是将mixin的内容混合到Vue的初始参数options中

    为什么是beforeCreate而不是created呢?因为如果是在created操作的话,$options已经初始化好了

    如果当前是根实例:那么就会添加_routerRoot和_router属性,this._routerRoot也就是拿到根实例,this._router就是传入的router

            this._routerRoot = this
            this._router = this.$options.router
    
    • 1
    • 2

    如果不是根实例:也会添加_routerRoot属性,如果

            this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
    
    • 1

    这样每个组件中都可以拿到可以获取到根组件和传入的router了,子组件可以通过this.$parent._router.$options也就是this._routerRoot._router来拿到传入的router了

    判断了是不是根组件是根据有没有传入router属性,在main.js入口文件中,我们是传入了router属性,但是在App.vue组件中,我们不会传入router

    new Vue({
      router,
      store,
      render: (h) => h(App),
    }).$mount('#app')
    
    • 1
    • 2
    • 3
    • 4
    • 5

    为什么判断当前组件是子组件,就可以通过父组件拿到_root根组件呢

    父beforeCreate-> 父created -> 父beforeMount -> 子beforeCreate ->子create ->子beforeMount ->子 mounted -> 父mounted
    
    • 1

    在执行子组件的beforeCreate的时候,父组件已经执行完beforeCreate了,那理所当然父组件拿到父组件中的_router,父组件又会向上一级父组件查找,一种递归式的查找

    vue-router中hash与history路由模式的区别

    他们的区别主要是因为他们的实现原理不同

    1》形式上,哈希模式有#号,历史模式没有;

    哈希有#号,不美观;如果考虑url的规范那么就需要使用history模式,因为history模式没有#号,是个正常的url,适合推广宣传

    2》监听的事件不一样

    hash模式监听的是hashChange事件,history模式监听的是popState事件

    2》pushState() 设置的新 URL 可以是与当前 URL 同源的任意 URL;而 hash 只可修改 # 后面的部分,因此只能设置与当前 URL 同文档的 URL
    pushState() 设置的新 URL 可以与当前 URL 一模一样,这样也会把记录添加到栈中;而 hash 设置的新值必须与原来不一样才会触发动作将记录添加到栈中

    4》请求不同,历史模式会请求完整的url地址,而哈希模式只会请求#前面的地址,所以需要设置404的代理;而哈希不会,默认会展示之前页面的内容,但是你可以配置对应404页面

    5》哈希模式的SEO不好;历史模式SEO相对会更好

    6》push 和 replace 方法的实现不一样

    使用中常见的一些处理

    哈希模式配置404:

      {
        path: '/404',
        component: () => import('../views/404.vue'),
      },
      {
        path: '*',
        redirect: '/404',
      },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    history模式处理404

        location / {
         try_files $uri $uri/ /index.html$args;
        }
    
    • 1
    • 2
    • 3

    当请求的文件或目录不存在时,将请求重定向到index.html。这使得所有的路由请求都指向了Vue应用的入口页面

    在开发环境需要设置historyApiFallback

      devServer: {
        historyApiFallback: {
          index: '/index.html',
        },
      },
    
    • 1
    • 2
    • 3
    • 4
    • 5

    this.$router.push()如果是跳和当前路由一样的路由,就会报错,下面就捕获了下错误:

    const originalPush = VueRouter.prototype.push
    const originalReplace = VueRouter.prototype.replace
    VueRouter.prototype.push = function (location, onResolve, onReject) {
      if (onResolve || onReject)
        return originalPush.call(this, location, onResolve, onReject)
      return originalPush.call(this, location).catch((err) => err)
    }
    VueRouter.prototype.replace = function (location, onResolve, onReject) {
      if (onResolve || onReject)
        return originalReplace.call(this, location, onResolve, onReject)
      return originalReplace.call(this, location).catch((err) => err)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
  • 相关阅读:
    MySQL常见操作
    【CNN-FPGA开源项目解析】卷积层03--单格乘加运算单元PE & 单窗口卷积块CU 模块
    css中的一些符号代表什么选择器
    etcd的安装和使用
    数值分析笔记(二)函数插值
    京东一面:如何在SpringBoot启动时执行特定代码?有哪些方式?
    NoSQL-Redis配置与优化
    CSDN云IDE初次测评体验
    04-快速掌握Redis,了解Redis中常见的结构类型及其应用场景
    华为机试 - 最小调整顺序次数
  • 原文地址:https://blog.csdn.net/weixin_50576800/article/details/132796280