精华内容
下载资源
问答
  • Vue Router

    千次阅读 2020-07-19 23:38:54
    Vue RouterVue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。



    在这里插入图片描述



    1.1 路由简介

    1.1.1 概述

      路由的实质是一种对应关系,url 与资源之间的对应关系就是路由。路由分为前端路由和后端路由,后端路由是由服务器完成转发,前端路由是 hash(锚点) 的变化实现的。Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。包含的功能有:
     ♞ 嵌套的路由/视图表
     ♞ 模块化的、基于组件的路由配置
     ♞ 路由参数、查询、通配符
     ♞ 基于 Vue.js 过渡系统的视图过渡效果
     ♞ 细粒度的导航控制
     ♞ 带有自动激活的 CSS class 的链接
     ♞ HTML5 历史模式或 hash 模式,在 IE9 中自动降级
     ♞ 自定义的滚动条行为

    在这里插入图片描述




    1.2 Vue Router 的使用

    1.2.1 语法

    ☞ 安装

    # npm 安装
    npm install vue-router
    
    # 使用 CDN
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
    

    ☞ 语法(src\router/index.js)

    import Vue from 'vue'
    import Router from 'vue-router'
    // 增加这一行, 作用是引入 MyPage 这个组件
    import MyPage from '@/components/MyPage'
    
    Vue.use(Router)
    export default new Router({
      routes: [
        // 增加下面几行, 表示定义了  /#/myPage 这个url
        {
          path: '/myPage',
          name: 'MyPage',	// 可以省略,加上 name 属性之后就是命名路由
          component: MyPage
        },
      ]
    })
    
    <!-- 视图中跳转方式 -->
    <!-- 字符串 -->
    <router-link to="myPage"> to apple</router-link>
    
    <!-- 对象 -->
    <router-link :to="{path:'myPage'}"> to apple</router-link>
    
    <!-- 命名路由 -->
    <router-link :to="{name: 'MyPage'}"> to apple</router-link>
    
    // JS 中跳转方式
    // 字符串
    this.$router.push('/myPage')
    
    // 对象
    this.$router.push({ path: '/myPage' })
    
    // 命名的路由
    this.$router.push({ name: 'MyPage'})
    

    ☞ 说明
      路由是所有前端框架中都必须具备的元素。 它定义了对于那个 URL(页面),应该由那个文件来处理。每个 vue 页面,都要对应一个路由。一般需要两个东西:vue 文件,负责展示页面;路由代码,让 url 与 vue 文件对应。路由中的相关属性:
    path:定义了 链接地址,
    name:为这个路由加个名字,方便以后引用,例如: this.$router.push({name: 'myPage'})
    component:该路由由哪个 component 来处理。

    1.2.2 示例

    ☞ 项目目录
    在这里插入图片描述

    ☞ 组件(src\components\HelloWorld.vue)

    <template>
      <div class="hello">
        <h1>{{ msg }}</h1>
        <p>
          For a guide and recipes on how to configure / customize this project,<br>
          check out the
          <a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
        </p>
        <h3>Installed CLI Plugins</h3>
        <ul>
          <li>babel</li>
          <li>eslint</li>
          <li>router</li>
        </ul>
        <h3>Essential Links</h3>
        <ul>
          <li>Core Docs</li>
          <li>Forum</li>
          <li>Community Chat</li>
          <li>Twitter</li>
          <li>News</li>
        </ul>
        <h3>Ecosystem</h3>
        <ul>
          <li>vue-router</li>
          <li>vuex</li>
          <li>vue-devtools</li>
          <li>vue-loader</li>
          <li>awesome-vue</li>
        </ul>
      </div>
    </template>
    
    <script>
    export default {
      name: 'HelloWorld',
      props: {
        msg: String
      }
    }
    </script>
    
    <!-- Add "scoped" attribute to limit CSS to this component only -->
    <style scoped lang="less">
    h3 {
      margin: 40px 0 0;
    }
    ul {
      list-style-type: none;
      padding: 0;
    }
    li {
      display: inline-block;
      margin: 0 10px;
    }
    a {
      color: #42b983;
    }
    </style>
    

    ☞ about 页面(src\views\About.vue)

    <template>
      <div class="about">
        <h1>This is an about page</h1>
      </div>
    </template>
    

    ☞ home 页面(src\views\Home.vue)

    <template>
      <div class="home">
        <img alt="Vue logo" src="../assets/logo.png">
        <HelloWorld msg="Welcome to Your Vue.js App"/>
      </div>
    </template>
    
    <script>
    // @ is an alias to /src
    import HelloWorld from '@/components/HelloWorld.vue'
    
    export default {
      name: 'Home',
      components: {
        HelloWorld
      }
    }
    </script>
    

    ☞ 主页面(src\App.vue)

    <template>
      <div id="app">
        <div id="nav">
          <router-link to="/">Home</router-link> |
          <router-link to="/about">About</router-link>
        </div>
        <router-view/>
      </div>
    </template>
    
    <style lang="less">
    #app {
      font-family: Avenir, Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
    }
    
    #nav {
      padding: 30px;
    
      a {
        font-weight: bold;
        color: #2c3e50;
    
        &.router-link-exact-active {
          color: #42b983;
        }
      }
    }
    </style>
    

    ☞ 路由(src\router\index.js)

    import Vue from 'vue'
    import VueRouter from 'vue-router'
    import Home from '../views/Home.vue'
    
    Vue.use(VueRouter)
    
      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')
      }
    ]
    
    const router = new VueRouter({
      routes
    })
    
    export default router
    

    ☞ 演示
    在这里插入图片描述


    1.2.3 路由的参数

    ☞ 普通参数

    routes: [
      {
        path: '/blog',
        name: 'Blog'
      },
    ]
    

      对于上述路由我们想要传递参数可以在视图中使用 <router-link :to="{name: 'Blog', query:{id: 3} }">User</router-link>,也可以在 JS 中使用 this.$router.push({name: 'Blog', query: {id: 3}}),这两种方式都会将页面跳转到 跳转到 /#/blog?id=3 这个 URL 上。然后使用 this.$route.query.id 就可以获取到参数。

    ☞ 路由中声明的参数

    routes: [
      {
        path: '/blog/:id',
        name: 'Blog'
      },
    ]
    

      对于这种路由中将参数声明了的,我们想要传递参数可以在视图中使用 <router-link :to="{name: 'Blog', params: {id: 3} }">User</router-link>,也可以在 JS 中使用 this.$router.push({name: 'Blog', params: {id: 3}}),这两种方式都会将页面跳转到 跳转到 /#/blog/3 这个 URL 上。然后使用 this.$route.params.id 就可以获取到参数。

    1.2.4 动态路由

    ☞ 概述

      我们经常需要把某种模式匹配到的所有路由,全都映射到同个组件。例如,我们有一个 User 组件,对于所有 ID 各不相同的用户,都要使用这个组件来渲染。那么,我们可以在 vue-router 的路由路径中使用“动态路径参数”(dynamic segment) 来达到这个效果。

    模式 匹配路径 $route.params
    /user/:username /user/evan { username: ‘evan’ }
    /user/:username/post/:post_id /user/evan/post/123 { username: ‘evan’, post_id: ‘123’ }

    ☞ 示例

    routes: [
    	// 动态路径参数 以冒号开头
    	{ path: '/user/:id', component: User }
    ]
    
    
    routes: [
    	// 不用动态路由,需要配置一堆
    	{ path: '/user/1', component: User },
    	{ path: '/user/2', component: User },
    	{ path: '/user/2', component: User },
    ]
    

    1.2.5 嵌套路由

    ☞ 概述

      实际生活中的应用界面,通常由多层嵌套的组件组合而成。同样地,URL 中各段动态路径也按某种结构对应嵌套的各层组件,借助 vue-router,使用嵌套路由配置,就可以很简单地表达这种关系。
    在这里插入图片描述

    ☞ 示例

    routes: [
    	{ path: '/user', component: User,
    		children: [
    			{
    				// 当 /user/profile 匹配成功,
    				// UserProfile 会被渲染在 User 的 <router-view> 中
    				path: 'profile',
    				component: UserProfile
    			},
    			{
    				// 当 /user/posts 匹配成功
    				// UserPosts 会被渲染在 User 的 <router-view> 中
    				path: 'posts',
    				component: UserPosts
    			}
    		]
    	}
    ]
    

    1.2.6 编程式导航

      除了使用 <router-link> 创建 a 标签来定义导航链接,我们还可以使用 router.push(location,onComplete,onAbort) 方法来实现。使用 router.push 方法会向 history 栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,则回到之前的 URL。当你点击 <router-link> 时,router.push 方法会在内部调用,所以说,点击 <router-link :to="..."> 等同于调用 router.push(...)router.go(n) 这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步,类似 window.history.go(n)

    // 字符串
    router.push('home')
    
    // 对象
    router.push({ path: 'home' })
    
    // 命名的路由
    router.push({ name: 'user', params: { userId: '123' }})
    
    // 带查询参数,变成 /register?plan=private
    router.push({ path: 'register', query: { plan: 'private' }})
    

      注意:如果提供了 path 属性,params 属性会被忽略,query 属性并不属于这种情况。一般我们需要提供路由的 name 或手写完整的带有参数的 path。

    1.2.7 重定向与别名

    ☞ 重定向

    // 重定向可以通过 routes 配置来完成
    routes: [
    	{ path: '/a', redirect: '/b' }
    ]
    
    // 重定向的目标也可以是一个命名的路由
    routes: [
    	{ path: '/a', redirect: { name: 'foo' }}
    ]
    
    // 重定向甚至是一个方法,动态返回重定向目标
    routes: [
    	{ path: '/a', redirect: to => {
    		// 方法接收 目标路由 作为参数
    		// return 重定向的 字符串路径/路径对象
    	}}
    ]
    

    ☞ 别名

      /a 的别名是 /b,意味着,当用户访问 /b 时,URL 会保持为 /b,但是路由匹配则为 /a,就像用户访问 /a 一样。“别名”的功能让你可以自由地将 UI 结构映射到任意的 URL,而不是受限于配置的嵌套路由结构。

    routes: [
    	{ path: '/a', component: A, alias: '/b' }
    ]
    




    在这里插入图片描述

    展开全文
  • 前言 当程序加载执行完vue-router文件,就执行new VueRouter()...在第一篇文章中的实例中,调用new VueRouter()来创建router对象,VueRouter()具体的处理逻辑如下: 从上面的处理逻辑中可以看出,new VueRout...

    前言

    当程序加载执行完vue-router文件,就执行new VueRouter()动作以及new Vue()动作,本篇文章就是探究这两个过程对于路由具体的处理逻辑。

    具体分析

    new VueRouter()

    在第一篇文章中的实例中,调用new VueRouter()来创建router对象,VueRouter()具体的处理逻辑如下:
    这里写图片描述
    从上面的处理逻辑中可以看出,new VueRouter()创建路由对象就是初始化属性,而最为重要的就是mode模式问题了

    mode模式支持’hash’、’history’、’abstract’,前两者是浏览器环境下,最后一个是支持Js的非浏览器环境


    根据mode模式调用不同的构造函数生成不同的History对象,该对象是实现路由的核心对象之一

    HashHistory构造函数

    实际在加载解析vue-router时就已经执行了HashHistory

    该函数本身是一个立即执行函数,主要的功能就是定义HashHistory相关的方法和构造函数以及实现继承History

    在源码中History是父对象,无论是HashHistory、HTML5History还是AbstractHistory都继承自History

    创建HashHistory最主要的功能点就是执行History构造函数

    // History$$1就是History
    History$$1.call(this, router, base);

    而History对象定义了路由操作的最基本的操作,例如updateRoute等,而其构造函数就是定义需要用到的属性, 主要的属性如下:

    • router:当前路由对象
    • base:基本路径
    • current:当前路由模块,默认是START模块就是在加载解析是创建的path为/的默认路径

    new Vue()

    new Vue({
        router
    }).$mount('#app')

    这步操作实际上会调用VueRouter的beforeCreate生命周期函数
    在上一篇文章中说了,在加载解析的过程中会调用install函数,而该函数中最重要的一点就是对所有组件全局混入beforeCreate和destroyed。

    在new Vue()这步会调用beforeCreate生命周期函数,而这边是路由功能实现的触发点,具体看看该生命周期的处理逻辑:

        beforeCreate: function beforeCreate () {
          // 如果当前Vue实例存在router配置属性
          if (isDef(this.$options.router)) {
            // 当前Vue实例
            this._routerRoot = this;
            // 当前VueRouter实例对象
            this._router = this.$options.router;
            // 调用VueRouter.prototype.init
            this._router.init(this);
            // 定义响应属性_route
            Vue.util.defineReactive(this, '_route', this._router.history.current);
          } else {
            // 若父组件存在_routerRoot则当前Vue组件实例中_routerRoot与相同,否则就是当前Vue实例对象
            this._routerRoot = (this.$parent && this.$parent._routerRoot) || this;
          }
          registerInstance(this, this);
        },

    这里Vue实例$options是存在router属性的,所以会调用VueRoute对象的init方法。

    init

    实际上在上一章就分析了这边的处理逻辑,就是定义Vue根实例对象,以及history的处理,而history的处理是init很关键的地方。

    var history = this.history;
    // history模式、hash模式的处理
    if (history instanceof HTML5History) {
       history.transitionTo(history.getCurrentLocation());
    } else if (history instanceof HashHistory) {
       var setupHashListener = function () {
         history.setupListeners();
       };
       history.transitionTo(
         history.getCurrentLocation(),
         setupHashListener,
         setupHashListener
       );
     }
    
     history.listen(function (route) {
       this$1.apps.forEach(function (app) {
         app._route = route;
       });
     });

    从上面的代码可以看出,实际上是调用history对象的transitionTo、listen方法,实际上可以猜测出这两个方法的大概的功能:

    • transitionTo:路由切换
    • listen:监听

    当然还是要看看它们的具体实现逻辑,首先来看看trsitionTo方法。

    transitionTo

    首先看看传递给transitionTo方法的参数,有三个:

    • history.getCurrentLocation()
    • 另外两个参数都是history.setupListeners()

    getCurrentLocation:是获取window.location.href中#之后的路径,就是路由路径
    setupListeners:是监听popstate或hashchange事件

    首先来看看setupListeners,这里是vue-router实现的核心

    // 当前的history对象
    var this$1 = this;
    var router = this.router;
    // 切换新路由的页面滚动位置的处理,在支持pushState的前提下
    var expectScroll = router.options.scrollBehavior;
    var supportsScroll = supportsPushState && expectScroll;
    
    if (supportsScroll) {
      setupScroll();
    }
    
    // 监听popstate或hashchange事件
    window.addEventListener(supportsPushState ? 'popstate' : 'hashchange', 
    function () {
      // 获取当前路由
      var current = this$1.current;
      if (!ensureSlash()) {
        return
      }
      // 路由切换
      this$1.transitionTo(getHash(), function (route) {
        if (supportsScroll) {
          handleScroll(this$1.router, route, current, true);
        }
        // hash模式下,实际上是执行window.location.replace方法
        if (!supportsPushState) {
           replaceHash(route.fullPath);
        }
      });
    });

    从上面可知在实现路由切换都涉及到了transitionTo方法,就看看transitionTo的实现逻辑。

    transitionTo内部实际上是调用confirmTransition方法

    confirmTransition方法主要的点就是:

    • 判断是否是相同模块,做具体的处理

    • 应用beforeRouterEnter等钩子

    • 执行uploadRoute,更新当前路由

    • 执行传入的函数,实现注册监听onhashchange事件

    listen

    该方法就是替换Vue实例中的_route响应属性

    总结

    上面的总结就是分析了整个大概流程,VueRouter的实现细节蛮多的,实际上通过上面的基本知晓VueRouter整个流程:

    • new VueRouter()实际上主要就是history对象的实现,该对象是实现路由操作的核心

    • new Vue()时会触发组件的beforeCreate生命周期,调用VueRouter的init方法,完成默认路由的切换以及路由的监听

    下一篇文章会分析路由切换时的处理流程。

    展开全文
  • 1,vue中使用vueRouter需要通过vue.use(vueRouter),众所周知,这里实际上调用了VueRouter的install方法,对Vue进行了扩展,vueRouter的install方法如下: function install (Vue) { //如果已经挂载了,跳过 if ...

    1,vue中使用vueRouter需要通过vue.use(vueRouter),众所周知,这里实际上调用了VueRouter的install方法,对Vue进行了扩展,vueRouter的install方法如下:

    function install (Vue) {
    //如果已经挂载了,跳过
      if (install.installed && _Vue === Vue) { return }
    //如果未挂载过, installed属性改为true
      install.installed = true;
    //获取当前vue
      _Vue = Vue;
    
      var isDef = function (v) { return v !== undefined; };
    
      var registerInstance = function (vm, callVal) {
        var i = vm.$options._parentVnode;
        if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
          i(vm, callVal);
        }
      };
    //在vue的beforeCreate钩子和destroyed钩子中新增关于路由的处理
      Vue.mixin({
        beforeCreate: function beforeCreate () {
          if (isDef(this.$options.router)) {
            this._routerRoot = this;
            this._router = this.$options.router; //new VueRouter()
            this._router.init(this); //如果Vue实例参数中,有指定的router属性,执行init初始化
    //调用vue.util里的方法,通过get和set劫持this._route,router-view实时更新
            Vue.util.defineReactive(this, '_route', this._router.history.current);
          } else {
            this._routerRoot = (this.$parent && this.$parent._routerRoot) || this;
          }
          registerInstance(this, this);
        },
        destroyed: function destroyed () {
          registerInstance(this);
        }
      });
    //给vue新增$router, $route属性,并指定get方法返回值,$route属性。于是就有了,this.$router和this.$route对象可以使用。
      Object.defineProperty(Vue.prototype, '$router', {
    //this._routerRoot => this (即vue)
    //this._router =>new VueRouter()
        get: function get () { return this._routerRoot._router } 
      });
    
      Object.defineProperty(Vue.prototype, '$route', {
        get: function get () { return this._routerRoot._route }
      });
    //新增子组件RouterView, RouterLink
      Vue.component('RouterView', View);
      Vue.component('RouterLink', Link);
    
      var strats = Vue.config.optionMergeStrategies;
      // 默认一下组件内导航钩子跟created方法一样
      strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created;
    }

    对于配置了路由的vue实例,install方法中做了如下两步操作:

    1. this._router = this.$options.router; 获取VueRouter实例(传入的router:router 对象, router = new VueRouter({routes:[]}))

    2. this._router.init(this) 重点, 执行VueRouter的init方法

    ---------------------------------------分割线-------------------------------------

    第一步: new VueRouter({routes:[]})

    
    
    var VueRouter = function VueRouter (options) {
      if ( options === void 0 ) options = {};
    
      this.app = null;
      this.apps = [];
      this.options = options;
      this.beforeHooks = [];
      this.resolveHooks = [];
      this.afterHooks = [];
    //调用createRouteMap方法,遍历routes,执行addRouteRecord(递归)生成的record对象存入
    //pathList pathMap nameMap。
    
    //createRouteMap最后 return { pathList: pathList, pathMap: pathMap, nameMap: nameMap }
      this.matcher = createMatcher(options.routes || [], this);
    
      var mode = options.mode || 'hash';
    //如果当前环境不支持history模式,强制切换到hash模式
      this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false;
      if (this.fallback) {
        mode = 'hash';
      }
    //如果不是浏览器环境,切换到abstract模式
      if (!inBrowser) {
        mode = 'abstract';
      }
      this.mode = mode;
    //根据mode值创建不同的实例,生成不同的history对象
      switch (mode) {
        case 'history':
          this.history = new HTML5History(this, options.base);
          break
        case 'hash':
          this.history = new HashHistory(this, options.base, this.fallback);
          break
        case 'abstract':
          this.history = new AbstractHistory(this, options.base);
          break
        default:
          {
            assert(false, ("invalid mode: " + mode));
          }
      }
    };

    所以构造函数中做了两件事:

         1》对routes数组进行匹配处理;

                

         2》根据mode,创建不同的实例, HTML5History, HashHistory, AbstractHistory

    第二步: VueRouter.prototype.init初始化, 主要是根据this.history的原型类型,执行对应的transitionTo方法。

    例如:项目中用的hash模式,那么在VueRouter构造方法中会创建出HashHistory实例,init方法中执行HashHistory的transitionTo方法

    VueRouter.prototype.init = function init (app /* Vue component instance */) {
        var this$1 = this;
    
      "development" !== 'production' && assert(
        install.installed,
        "not installed. Make sure to call `Vue.use(VueRouter)` " +
        "before creating root instance."
      );
        //app 当前vue
      this.apps.push(app);
      
      app.$once('hook:destroyed', function () {
        // 监听到destroyed钩子时,从apps数组删除该vue实例
        var index = this$1.apps.indexOf(app);
        if (index > -1) { this$1.apps.splice(index, 1); }
        if (this$1.app === app) { this$1.app = this$1.apps[0] || null; }
      });
    
      if (this.app) {
        return
      }
        //this.app 为当前vue
      this.app = app;
    
      var history = this.history;
    //根据history的原型类型,自行对应的transitionTo方法
      if (history instanceof HTML5History) {
        history.transitionTo(history.getCurrentLocation());
      } else if (history instanceof HashHistory) {
        var setupHashListener = function () {
          history.setupListeners();
        };
        history.transitionTo(
          history.getCurrentLocation(),
          setupHashListener,
          setupHashListener
        );
      }
    
      history.listen(function (route) {
        this$1.apps.forEach(function (app) {
          app._route = route;
        });
      });
    };

    ------------以hash模式    HashHistory实例为例-------------

    HashHistory构造方法:HashHistory继承自History

    // 在VueRouter构造函数中,this.history = new HashHistory(this, options.base, this.fallback); 
    
    //options.base指的是new VueRouter时传入的base属性
    
    var HashHistory = /*@__PURE__*/(function (History$$1) {
    //router -> VueRouter
      function HashHistory (router, base, fallback) {
        History$$1.call(this, router, base);
        // check history fallback deeplinking
        if (fallback && checkFallback(this.base)) {
          return
        }
        ensureSlash();
      }
     if ( History$$1 ) HashHistory.__proto__ = History$$1;
      HashHistory.prototype = Object.create( History$$1 && History$$1.prototype );
      HashHistory.prototype.constructor = HashHistory;
      ...
      return HashHistory;
    }(History));

    在上面的HashHistory自执行方法中,指定了HashHistory继承自History,并指定了HashHistory一些原型方法:

    1.HashHistory.prototype.setupListeners

    //是Android 2.或者Android 4.0且是Mobile Safari,不是Chorme, Windows Phone的返回false,否则返回true
    var supportsPushState = inBrowser && (function () {
      var ua = window.navigator.userAgent;
    
      if (
        (ua.indexOf('Android 2.') !== -1 || ua.indexOf('Android 4.0') !== -1) &&
        ua.indexOf('Mobile Safari') !== -1 &&
        ua.indexOf('Chrome') === -1 &&
        ua.indexOf('Windows Phone') === -1
      ) {
        return false
      }
    
      return window.history && 'pushState' in window.history
    })();
    • 获取用户定义的scrollBehavior
    • 给window添加事件监听addEventListener, 如果supportsPushState,支持pushState,监听popstate, 否则监听hashchange;
    • 监听回调里,调用实例的transitionTo
    window.addEventListener(
          supportsPushState ? 'popstate' : 'hashchange',
          function () {
            var current = this$1.current;
            if (!ensureSlash()) {
              return
            }
            this$1.transitionTo(getHash(), function (route) {
              if (supportsScroll) {
                handleScroll(this$1.router, route, current, true);
              }
              if (!supportsPushState) {
                replaceHash(route.fullPath);
              }
            });
          }
        );

    注释: getHash() 做了以下工作: 获取hash值和查询参数

                    1.获取window.location.href

                     2. 获取‘#’的索引值index,并截取index+1后的内容

                     3. 从截取的内容里获取‘?’查询参数的开始索引 seatchIndex

                                  3.1如果没有查询参数, 在获取一次‘#’索引,确保获取了所有的hash值,并进行decodeURI编码,并返回

                                   3.2如果有查询参数,把hash和查询参数一起decodeURI, 并返回

    2.HashHistory.prototype.push(location, onComplete, onAbort)

    this.transitionTo(), 跳转到指定路由, 回调用执行pushHash, handleScroll, onComplete

    3.HashHistory.prototype.replace

    this.transitionTo(), 跳转到指定路由, 回调用执行replaceHash, handleScroll, onComplete

    注释:  pushHash(path)做了以下工作: 如果supportsPushState, 执行pushState(完整url); 否则window.location.hash= path

                  replaceHash(path)做了以下工作: 如果supportsPushState,执行replaceState(getUrl(path));否则window.location.replace(getUrl(path));

                  pushState(url replace) : 调用window.history方法,如果replace传入true,history.replaceState, 否则history.pushState

    4.HashHistory.prototype.go(n)

    window.history.go(n);

    5.HashHistory.prototype.ensureURL(push)

    if (getHash() !== current) {
    
        push ? pushHash(current) : replaceHash(current);
    
    }

    6.HashHistory.prototype.getCurrentLocation

         return getHash()

    -----------------------------------------------------------------------------------------------------------------------------------

    综上:

    在hashHistory的原型方法,setupListeners(), push(location, onComplete, onAbort), replace(location, onComplete, onAbort),中都调用了this.transitionTo(),而这个方法是在History的原型上定义的。

    ----------------------下面先分析一下History做了什么-----------------------

    //new HashHistory()时 传入的是router-》 当前VueRouter实例, base 实例初始时传入的base属性
    
    var History = function History (router, base) {
      this.router = router; //当前VueRouter实例
      this.base = normalizeBase(base);
      // start with a route object that stands for "nowhere"
      this.current = START;   // {path: '/'}
      this.pending = null;
      this.ready = false;
      this.readyCbs = [];
      this.readyErrorCbs = [];
      this.errorCbs = [];
    };

    最重要的方法,通用的transitionTo 方法

    //transitionTo 方法
    History.prototype.transitionTo = function transitionTo (location, onComplete, onAbort) {
        var this$1 = this;
        //重点1 
        //location => 当前要push或者replace的路由 
        // this.current => {path: '/'}
        var route = this.router.match(location, this.current); 
    
    //调用this.confirmTransition方法,完成路由跳转, 重点2
      this.confirmTransition(route, function () {
        this$1.updateRoute(route);
        onComplete && onComplete(route);
        this$1.ensureURL();
    
        // fire ready cbs once
        if (!this$1.ready) {
          this$1.ready = true;
    //执行回调
          this$1.readyCbs.forEach(function (cb) { cb(route); });
        }
      }, function (err) {
        if (onAbort) {
          onAbort(err);
        }
        if (err && !this$1.ready) {
          this$1.ready = true;
          this$1.readyErrorCbs.forEach(function (cb) { cb(err); });
        }
      });
    };

    重点1: 

    var route = this.router.match(location, this.current);  

    调用VueRouter实例的match方法,最终调用了createMatcher(options.routes || [], this).match(raw, current, redirectedFrom);
    createMatcher方法,返回一个对象,包含match方法属性和addRoutes属性

    function createMatcher (
      routes,
      router
    ) {
      var ref = createRouteMap(routes);
      var pathList = ref.pathList;
      var pathMap = ref.pathMap;
      var nameMap = ref.nameMap;
    
    //匹配路由,并创建路由
      function match (
        raw,
        currentRoute,
        redirectedFrom
      ) { 
        var location = normalizeLocation(raw, currentRoute, false, router);
        var name = location.name;
        if(name){
            var record = nameMap[name] || null ;
            ...
        } else {
           record = null
            }
        return  _createRoute(record , location)
    
        }
    
    //重定向或者创建route
      function _createRoute (
        record,
        location,
        redirectedFrom
      ) {
        if (record && record.redirect) {
          return redirect(record, redirectedFrom || location)
        }
        if (record && record.matchAs) {
          return alias(record, location, record.matchAs)
        }
        return createRoute(record, location, redirectedFrom, router)
      }
    
     function addRoutes (routes) {
        createRouteMap(routes, pathList, pathMap, nameMap);
      }
      ...
      return {
        match: match,
        addRoutes: addRoutes
      }
    }
    

       match方法,经过各种处理,最终返回的是createRoute()方法的返回值,也就是最终的返回值就是当前要跳转路由的路由对象,也就是 route的结果, 如下代码所示:

    
    //createRoute方法返回值大致如下, 也就是match最终返回的结果格式
    createRoute() => return Object.freeze(
    {
        name: location.name || (record && record.name),
        meta: (record && record.meta) || {},
        path: location.path || '/',
        hash: location.hash || '',
        query: query,
        params: location.params || {},
        fullPath: getFullPath(location, stringifyQuery$$1),
        matched: record ? formatMatch(record) : []
    }
    ) 

      

    重点2:

    route就是上面的当前路由对象

    History.prototype.confirmTransition(route, onComplete, onAbort)接受三个参数, 当前route, 完成的回调, 终止的回调

    History.prototype.confirmTransition = function confirmTransition (route, onComplete, onAbort) {
        var this$1 = this;
    
      var current = this.current;
    //终止路由
      var abort = function (err) {
        ...
        onAbort && onAbort(err);
      };
    //相同的路由,不跳转
      if (
        isSameRoute(route, current) &&
        route.matched.length === current.matched.length
      ) {
        this.ensureURL();
        return abort()
      }
    //处理路由队列,获取当前需要更新的路由,失效的路由,和要激活的路由
      var ref = resolveQueue(this.current.matched, route.matched);
        var updated = ref.updated;
        var deactivated = ref.deactivated;
        var activated = ref.activated;
    
      var queue = [].concat(
        extractLeaveGuards(deactivated),
        this.router.beforeHooks,
        extractUpdateHooks(updated),
        activated.map(function (m) { return m.beforeEnter; }),
        resolveAsyncComponents(activated)
      );
    
      this.pending = route;
      var iterator = function (hook, next) {
        if (this$1.pending !== route) {
          return abort()
        }
        try {
          hook(route, current, function (to) {
            if (to === false || isError(to)) {
              // next(false) -> abort navigation, ensure current URL
              this$1.ensureURL(true);
              abort(to);
            } else if (
              typeof to === 'string' ||
              (typeof to === 'object' && (
                typeof to.path === 'string' ||
                typeof to.name === 'string'
              ))
            ) {
              // next('/') or next({ path: '/' }) -> redirect
              abort();
    //路由跳转 , 调用实例的replace方法或者push方法, 更改url地址
              if (typeof to === 'object' && to.replace) {
                this$1.replace(to);
              } else {
                this$1.push(to);
              }
            } else {
              //执行路由的next方法
              next(to);
            }
          });
        } catch (e) {
          abort(e);
        }
      };
     //执行路由钩子和组件的激活及失活操作
      runQueue(queue, iterator, function () {
        var postEnterCbs = [];
        var isValid = function () { return this$1.current === route; };
        // wait until async components are resolved before
        // extracting in-component enter guards
        var enterGuards = extractEnterGuards(activated, postEnterCbs, isValid);
        var queue = enterGuards.concat(this$1.router.resolveHooks);
        runQueue(queue, iterator, function () {
          if (this$1.pending !== route) {
            return abort()
          }
          this$1.pending = null;
          onComplete(route);
          if (this$1.router.app) {
    //如果指定了app, 执行app的$nextTick方法
            this$1.router.app.$nextTick(function () {
              postEnterCbs.forEach(function (cb) { cb(); });
            });
          }
        });
      });
    };

    所以 confirmTransition方法主要做事情是:

       通过resolveQueue方法,获取要更新的路由,失活的组件,要激活的组件;

      完成url跳转(replace或者push方法)

      url改变完成后,执行组件的更新显示(如果组件有指定的vue实例,执行vue实例的$nextTick方法)

     

    总结: 

    1. 调用VueRouter.install方法,给vue安装路由

    2. 安装方法install中,主要做了三件事:

          2.1. 给vue的beforeCreate和destroyed钩子中新增关于路由的操作

          2.2. 给vue实例定义$router(当前路由实例new VueRouter) $route(当前路由对象)属性

          2.3. 给vue新增子组件 RouterView和RouterLink

    3. beforeCreate钩子中,获取vue实例参数中配置的router,即new VueRouter实例,执行实例的init方法;

    4. new VueRouter时, 根据传入的routes创建this.Matcher用于匹配路由,根据传入的mode等参数,新增对应的实例(HTML5History, HashHistory, AbstractHistory)赋值给this.history

    5.VueRouter.prototype.init方法中,主要做了两件事,

        5.1 根据this.history的原型类型,分别执行对应的transitionTo方法,其中HashHistory实例需要先执行setupListeners()方法

        5.2 调用history.listen()方法,获取当前route路由对象,this.$route的值

    6. HTML5History, HashHistory, AbstractHistory实例都继承自History,transitionTo定义在History的原型上。

    HTML5History监听了window对象的popstate事件

    HashHistory监听了window对象的hashchange事件或者popstate

    7. transitionTo中先获取当前路由对象,然后调用confirmTransition

    8. confirmTransition主要获取当前要更新的组件,失活的组件,重新激活的组件;通过调用继承自History的实例的replace或push方法完成 url的改变; 执行路由的导航钩子,并进行组件的显示隐藏更新操作。

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    展开全文
  • VueRouter之加载解析VueRouter文件

    千次阅读 2018-07-18 19:44:48
    分析的版本是VueRouter最新版本,是build之后的vue-router.js文件。 具体分析 从简单实例出发分析整个流程: html文件 &lt;!DOCTYPE html&gt; &lt;html lang="en"&gt; &lt;head...

    前言

    vue-router是Vue项目中组件化的核心,负责路由跳转,本系列会分析路由跳转的具体实现逻辑,目标是梳理路由跳转的实现逻辑。分析的版本是VueRouter最新版本,是build之后的vue-router.js文件。

    具体分析

    从简单实例出发分析整个流程:
    html文件

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Document</title>
      <script src="https://unpkg.com/vue/dist/vue.js"></script>
      <script src="./vue-router.js"></script>
     </head>
    <body>
      <div id="app">
        <h1>Hello App!</h1>
        <p>
          <router-link to="/foo">Go to Foo</router-link>
          <router-link to="/bar">Go to Bar</router-link>
        </p>
        <router-view></router-view>
      </div>
      <script>
        const Foo = { template: '<div>foo</div>' }
        const Bar = { template: '<div>bar</div>' }
        // 定义路由跳转关系
        const routes = [
          { path: '/foo', component: Foo },
          { path: '/bar', component: Bar }
        ]
        // 定义路由对象
        const router = new VueRouter({
          routes
        })
        // 将router注册到Vue中
        const app = new Vue({
          router
        }).$mount('#app')
      </script>
    </body>
    </html>

    效果:
    这里写图片描述

    通过script标签来加载解析vue-router文件,解析执行vue-router发生了什么呢?这是接下来需要分析的。

    VueRouter解析过程

    vue-router文件在加载解析过程中处理逻辑如下:
    这里写图片描述

    实际vue-router文件解析的过程做的主要事情有四点:

    • 定义router-view组件
    • 定义router-link组件
    • 创建初始路由Route对象
    • 满足浏览器环境以及Vue存在,就执行Vue.use(VueRouter)
    • 暴露VueRouter

    Vue插件的使用方式就是Vue.use(插件),此时会调用插件中的install方法

    所以Vue.use(VueRouter)实际上是调用VueRouter中的install方法,通过源码可知:

    VueRouter.install = install;

    接下来就具体分析install函数的逻辑。

    install

    install是创建的初始化过程,它的具体处理逻辑如下:
    这里写图片描述

    install函数中逻辑主要点有如下几点:

    • 保证初始化的唯一性
    • 混入beforeCreate和destroyed生命周期函数
    • 定义并监听routerroute属性
    • 在Vue.config.optionMergeStrategies上自定义beforeRouteEnter、beforeRouteLeave、beforeRouteUpdate混入,使用created的混入策略

    实际上最主要是第二条,在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);
    • 首先判断new Vue()实例中是否存在router属性, 在解析的时候还没有new Vue()所以此处为false
    • 存在就:
      • 设置router根实例是当前实例
      • 设置_router属性值为VueRouter实例对象
      • 调用实例对象的init(),进行初始化
      • 使用Vue.util中的defineReactive定义this对象的响应属性_route为当前路由模块
    • 不存在:就判断是否存在父组件,存在就将父组件中的_routeRoot赋值为当前实例的_routerRoot
    • 注册实例

    总结

    在vue-router初始化的过程中可以看到,实现上主要的工作就几点:

    • 全局注册router-view、router-link组件

    • 使用Vue.mixin往所有组件中混入beforeCreate、destroyed生命周期函数

    • beforeRouteLeave、beforeRouteEnter、beforeRouteUpdate自定义混合到组件中,混合策略与created相同

    • 暴露VueRouter构造函数

    下一篇文章主要分析new VueRouter的过程。

    展开全文
  • 写在前面:传参是前端经常需要用的一个操作,很多场景都会需要用到上个页面的参数,本文将会详细介绍vue router 是如何进行传参的,以及一些小细节问题。有需要的朋友可以做一下参考,喜欢的可以点波赞,或者关注...
  • Vue2+VueRouter2+Webpack+Axios 构建项目实战2017重制版(四)调整 App.vue 和 router 路由前情回顾在上一篇《Vue2+VueRouter2+Webpack+Axios 构建项目实战2017重制版(三)认识项目所有文件》,我们已经重新整理了...
  • Vue3中的Vue Router初探

    千次阅读 2020-05-07 17:13:12
    随着新版本的Vue Router处于Alpha阶段,我们已经可以开始查看下一个版本的Vue中它是如何工作的。 Vue3中的许多更改都会稍微改变我们访问插件和库的方式,其中包括Vue Router。我们将结合使用Alpha版本的Vue Router和...
  • vue3配合vue router4使用

    千次阅读 2020-09-27 16:07:15
    import { createRouter, createWebHistory } from "vue-router"; import index from '../views/index.vue' import one from '../views/one.vue' export default createRouter({ history: createWebHistory(), ...
  • Vue-router之集成 在项目目录新建config文件夹,新建router.js文件(Vue Router入口文件)和routes.js文件(路由配置文件),推荐项目结构 安装Vue Router插件 npm install vue-router -S 在 routes.js文件 ...
  • express routervue router的区别 express router express router路由指应用程序的端点(URI)如何响应客户端请求,是属于客户端路由,简单点来说就是常说的接口。 vue router vue router 属于前端路由模块,主要...
  • vue+vueRouter 实例demo

    万次阅读 2017-07-10 18:01:08
    main.js import Vue from 'vue' import App from './App.vue' import router from './router/router'... * @VueRouter:路由 */ /** * 定义常量信息 * @DOMAINNAME:客户端地址 * @SERVERNAME:服务端地址 *
  • Vue2+VueRouter2+webpack 构建项目实战(五)配置子路由

    万次阅读 多人点赞 2016-11-18 10:14:17
    Vue2+VueRouter2+webpack 构建项目实战(五)配置子路由前情回顾《Vue2+VueRouter2+webpack 构建项目实战(一)准备工作》《Vue2+VueRouter2+webpack 构建项目实战(二)目录以及文件结构》《Vue2+VueRouter2+...
  • Vue Router参数大全

    2020-05-03 10:31:21
    Vue Router对象创建 Vue Router构建选项详解 参数 类型 默认值 可选值 描述 routes Array 配置路由规则 mode string “hash” (浏览器环境) /“abstract” (Node.js 环境) “hash”/“history” / ...
  • vue router 参数获取

    2019-06-30 17:00:00
    vue router 参数获取通常是通过$route.query和$route.params方法这里将这两种方式通过代码展示出来: 路由代码: import Vue from 'vue' import Router from 'vue-router' import Home from './views/Home.vue...
  • Vue2+VueRouter2+webpack 构建项目实战(一)准备工作

    万次阅读 多人点赞 2016-11-15 14:43:06
    Vue2+VueRouter2+webpack 构建项目实战(一)准备工作之前写的博客没有采用打包工具,而是直接引用js的方式来做的。这种方式很扯淡,也因此,我写了三篇博客之后就没有再写了。通过几个月的学习和实战,基本厘清了...
  • Vue router的使用(学习笔记)

    千次阅读 2020-07-24 11:39:39
    Vue router的使用(学习笔记) 1、打开终端安装router命令如下 npm install vue-router或cnpm install vue-...import VueRouter from 'vue-router' Vue.use(VueRouter) 3、在src中新建view文件夹,在view新建例如
  • 模拟 VueRouter 的实现(简易版)

    千次阅读 2020-08-10 14:20:08
    模拟 VueRouter 前置的知识: 插件 slot 插槽 混入 render 函数 运行时和完整版的 Vue Vue Router 的核心代码 // 注册插件 // Vue.use() 内部调用传入对象的 install 方法 Vue.use(VueRouter); // 创建路由对象 ...
  • vue router全面详细知识点

    千次阅读 多人点赞 2020-05-21 08:33:03
    文章目录认识路由什么是路由后断路由前端路由前端路由的规则url的hashHTML5的history模式vue router基础什么是vue router安装和使用如何创建一个router实例挂载到vue实例中具体使用router创建实例的过程细节处理路由...
  • 1、express的router是属于后端的,Vuerouter是属于前端的。 2、服务端渲染时,express的router把数据和模板传给模板引擎的;客户端渲染时,通过路由去获得静态页面,浏览器发送http请求向服务端获取数据,Vue ...
  • vue router生命周期

    千次阅读 2019-04-24 10:14:01
    vue router生命周期 router中的参数 router异步解析执行,此时router在 resolve 完之前一直处于 等待中。 三个参数: to: Route: 即将要进入的目标 路由对象 from: Route: 当前导航正要离开的路由 ...
  • Vue2+VueRouter2+Webpack+Axios 构建项目实战2017重制版(五)配置 Axios api 接口调用文件前情回顾在上一篇《Vue2+VueRouter2+Webpack+Axios 构建项目实战2017重制版(四)调整 App.vue 和 router 路由》,我们通过...
  • webpack-vue-demowebpack+vue+vueRouter+es6 构建的简单实例项目 github地址 https://github.com/193Eric/webpack-vue-vueRouter Vue很轻量,它写组件的方式非常舒服。代码风格也很干净。之前也在网上看了很多...
  • Vue2.0+Vuex+VueRouter仿微信界面聊天室|仿微信聊天窗口|仿微信群聊 基于vue2.0+vuex+webpack2.0+es6+wcPop等技术开发的仿微信聊天室vue-weChatRoom实战项目,实现了下拉刷新聊天消息,发送信息、表情(gif),预览...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 44,163
精华内容 17,665
关键字:

vuerouter

vue 订阅