vue-router源码实例详解

本文主要和大家分享vue-router源码阅读学习,如同分析vuex源码我们首先通过一个简单例子进行了解vue-router是如何使用的,然后在分析在源码中是如何实现的,希望能帮助到大家。

示例

下面示例来自于example/basica/app.js

           

import Vue from 'vue'import VueRouter from 'vue-router'Vue.use(VueRouter)const Home = { template: '
home
' }const Foo = { template: '
foo
' }const Bar = { template: '
bar
' }const router = new VueRouter({  mode: 'history',  base: __dirname,  routes: [    { path: '/', component: Home },    { path: '/foo', component: Foo },    { path: '/bar', component: Bar }  ]})new Vue({  router,  template: `    
      

Basic

      
            
  • /
  •         
  • /foo
  •         
  • /bar
  •                   /bar              
          
  `}).$mount('#app')

登录后复制

首先调用Vue.use(VueRouter),Vue.use()方法是Vue用来进行插件安装的方法,这里主要用来安装VueRouter。然后实例化了VueRouter,我们来看看VueRouter这个构造函数到底做了什么。
从源码入口文件src/index.js开始看

           

import type { Matcher } from './create-matcher'export default class VueRouter {  constructor (options: RouterOptions = {}) {    this.app = null    this.apps = []    this.options = options    this.beforeHooks = []    this.resolveHooks = []    this.afterHooks = []    this.matcher = createMatcher(options.routes || [], this)    let mode = options.mode || 'hash'    this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false    if (this.fallback) {      mode = 'hash'    }    if (!inBrowser) {      mode = 'abstract'    }    this.mode = mode    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:        if (process.env.NODE_ENV !== 'production') {          assert(false, `invalid mode: ${mode}`)        }    }  }  init (app: any /* Vue component instance */) {    this.apps.push(app)    // main app already initialized.    if (this.app) {      return    }    this.app = app    const history = this.history    if (history instanceof HTML5History) {      history.transitionTo(history.getCurrentLocation())    } else if (history instanceof HashHistory) {      const setupHashListener = () => {        history.setupListeners()      }      history.transitionTo(        history.getCurrentLocation(),        setupHashListener,        setupHashListener      )    }    history.listen(route => {      this.apps.forEach((app) => {        app._route = route      })    })  }  getMatchedComponents (to?: RawLocation | Route): Array {    const route: any = to      ? to.matched        ? to        : this.resolve(to).route      : this.currentRoute    if (!route) {      return []    }    return [].concat.apply([], route.matched.map(m => {      return Object.keys(m.components).map(key => {        return m.components[key]      })    }))  }}

登录后复制

代码一步步看,先从constructor函数的实现,首先进行初始化我们来看看这些初始化条件分别代表的是什么

this.app表示当前Vue实例

立即学习“前端免费学习笔记(深入)”;

this.apps表示所有app组件

this.options表示传入的VueRouter的选项

this.resolveHooks表示resolve钩子回调函数的数组,resolve用于解析目标位置

this.matcher创建匹配函数

代码中有createMatcher()函数,来看看他的实现

           

function createMatcher (  routes,  router) {  var ref = createRouteMap(routes);  var pathList = ref.pathList;  var pathMap = ref.pathMap;  var nameMap = ref.nameMap;    function addRoutes (routes) {    createRouteMap(routes, pathList, pathMap, nameMap);  }   function match (    raw,    currentRoute,    redirectedFrom  ) {    var location = normalizeLocation(raw, currentRoute, false, router);    var name = location.name;    // 命名路由处理    if (name) {      // nameMap[name]的路由记录      var record = nameMap[name];      ...        location.path = fillParams(record.path, location.params, ("named route "" + name + """));        // _createRoute用于创建路由        return _createRoute(record, location, redirectedFrom)    } else if (location.path) {      // 普通路由处理    }    // no match    return _createRoute(null, location)  }  return {    match: match,    addRoutes: addRoutes  }}

登录后复制

createMatcher()有两个参数routes表示创建VueRouter传入的routes配置信息,router表示VueRouter实例。createMatcher()的作用就是传入的routes通过createRouteMap创建对应的map,和一个创建map的方法。
我们先来看看createRouteMap()方法的定义

           

function createRouteMap (  routes,  oldPathList,  oldPathMap,  oldNameMap) {  // 用于控制匹配优先级  var pathList = oldPathList || [];  // name 路由 map  var pathMap = oldPathMap || Object.create(null);  // name 路由 map  var nameMap = oldNameMap || Object.create(null);  // 遍历路由配置对象增加路由记录  routes.forEach(function (route) {    addRouteRecord(pathList, pathMap, nameMap, route);  });  // 确保通配符总是在pathList的最后,保证最后匹配  for (var i = 0, l = pathList.length; i 

createRouteMap()有4个参数:routes代表的配置信息,oldPathList包含所有路径的数组用于匹配优先级,oldNameMap表示name map,oldPathMap表示path map。createRouteMap就是更新pathList,nameMap和pathMap。nameMap到底代表的是什么呢?它是包含路由记录的一个对象,每个属性值名是每个记录的path属性值,属性值就是具有这个path属性值的路由记录。这儿有一个叫路由记录的东西,这是什么意思呢?路由记录就是 routes 配置数组中的对象副本(还有在 children 数组),路由记录都是包含在matched属性中例如

           
const router = new VueRouter({  routes: [    // 下面的对象就是 route record    { path: '/foo', component: Foo,      children: [        // 这也是个 route record        { path: 'bar', component: Bar }      ]    }  ]})

登录后复制

在上面代码中用一段代码用于给每个route添加路由记录,那么路由记录的实现是如何的呢,下面是addRouteReord()的实现

           

function addRouteRecord (  pathList,  pathMap,  nameMap,  route,  parent,  matchAs) {  var path = route.path;  var name = route.name;  var normalizedPath = normalizePath(    path,    parent  );  var record = {    path: normalizedPath,    regex: compileRouteRegex(normalizedPath, pathToRegexpOptions),    components: route.components || { default: route.component },    instances: {},    name: name,    parent: parent,    matchAs: matchAs,    redirect: route.redirect,    beforeEnter: route.beforeEnter,    meta: route.meta || {},    props: route.props == null      ? {}      : route.components        ? route.props        : { default: route.props }  };  if (route.children) {    route.children.forEach(function (child) {      addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs);    });  }  if (route.alias !== undefined) {    // 如果有别名的情况  }  if (!pathMap[record.path]) {    pathList.push(record.path);    pathMap[record.path] = record;  }}

登录后复制

addRouteRecord()这个函数的参数我都懒得说什么意思了,新增的parent也表示路由记录,首先获取path,name。然后通过normalizePath()规范格式,然后就是record这个对象的建立,然后遍历routes的子元素添加路由记录如果有别名的情况还需要考虑别名的情况然后更新path Map。

History

我们在回到VueRouter的构造函数中,往下看是模式的选择,一共这么几种模式一种history,hash和abstract三种。· 默认hash: 使用URL hash值作为路由,支持所有浏览器
· history: 依赖HTML5 History API和服务器配置
· abstract:支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式。
默认是hash,路由通过“#”隔开,但是如果工程中有锚链接或者路由中有hash值,原先的“#”就会对页面跳转产生影响;所以就需要使用history模式。
在应用中我们常用的基本都是history模式,下面我们来看看HashHistory的构造函数

           

var History = function History (router, base) {  this.router = router;  this.base = normalizeBase(base);  this.current = START;  this.pending = null;  this.ready = false;  this.readyCbs = [];  this.readyErrorCbs = [];  this.errorCbs = [];};

登录后复制

因为hash和history有一些相同的地方,所以HashHistory会在History构造函数上进行扩展下面是各个属性所代表的意义:

this.router表示VueRouter实例

this.base表示应用的基路径。例如,如果整个单页应用服务在 /app/ 下,然后 base 就应该设为
 “/app/”。normalizeBase()用于格式化base

this.current开始时的route,route使用createRoute()创建

this.pending表示进行时的route

this.ready表示准备状态

this.readyCbs表示准备回调函数

creatRoute()在文件src/util/route.js中,下面是他的实现

           

function createRoute (  record,  location,  redirectedFrom,  router) {  var stringifyQuery$$1 = router && router.options.stringifyQuery;  var query = location.query || {};  try {    query = clone(query);  } catch (e) {}  var route = {    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) : []  };  if (redirectedFrom) {    route.redirectedFrom = getFullPath(redirectedFrom, stringifyQuery$$1);  }  return Object.freeze(route)}

登录后复制

createRoute有三个参数,record表示路由记录,location,redirectedFrom表示url地址信息对象,router表示VueRouter实例对象。通过传入的参数,返回一个冻结的route对象,route对象里边包含了一些有关location的属性。History包含了一些基本的方法,例如比较重要的方法有transitionTo(),下面是transitionTo()的具体实现。

           

History.prototype.transitionTo = function transitionTo (location, onComplete, onAbort) {    var this$1 = this;  var route = this.router.match(location, this.current);  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); });    }  });};

登录后复制

首先match得到匹配的route对象,route对象在之前已经提到过。然后使用confirmTransition()确认过渡,更新route,ensureURL()的作用就是更新URL。如果ready为false,更改ready的值,然后对readyCbs数组进行遍历回调。下面来看看HTML5History的构造函数

           

var HTML5History = (function (History$$1) {  function HTML5History (router, base) {    var this$1 = this;    History$$1.call(this, router, base);    var initLocation = getLocation(this.base);    window.addEventListener('popstate', function (e) {      var current = this$1.current;      var location = getLocation(this$1.base);      if (this$1.current === START && location === initLocation) {        return      }    });  }  if ( History$$1 ) HTML5History.__proto__ = History$$1;  HTML5History.prototype = Object.create( History$$1 && History$$1.prototype );  HTML5History.prototype.constructor = HTML5History;  HTML5History.prototype.push = function push (location, onComplete, onAbort) {    var this$1 = this;    var ref = this;    var fromRoute = ref.current;    this.transitionTo(location, function (route) {      pushState(cleanPath(this$1.base + route.fullPath));      handleScroll(this$1.router, route, fromRoute, false);      onComplete && onComplete(route);    }, onAbort);  };  HTML5History.prototype.replace = function replace (location, onComplete, onAbort) {    var this$1 = this;    var ref = this;    var fromRoute = ref.current;    this.transitionTo(location, function (route) {      replaceState(cleanPath(this$1.base + route.fullPath));      handleScroll(this$1.router, route, fromRoute, false);      onComplete && onComplete(route);    }, onAbort);  };  return HTML5History;}(History))

登录后复制

在HTML5History()中代码多次用到了getLocation()那我们来看看他的具体实现吧

           

function getLocation (base) {  var path = window.location.pathname;  if (base && path.indexOf(base) === 0) {    path = path.slice(base.length);  }  return (path || '/') + window.location.search + window.location.hash}

登录后复制

用一个简单的地址来解释代码中各个部分的含义。例如http://example.com:1234/test/test.htm#part2?a=123,window.location.pathname=>/test/test.htm=>?a=123,window.location.hash=>#part2。
把我们继续回到HTML5History()中,首先继承history构造函数。然后监听popstate事件。当活动记录条目更改时,将触发popstate事件。需要注意的是调用history.pushState()或history.replaceState()不会触发popstate事件。我们来看看HTML5History的push方法。location表示url信息,onComplete表示成功后的回调函数,onAbort表示失败的回调函数。首先获取current属性值,replaceState和pushState用于更新url,然后处理滚动。模式的选择就大概讲完了,我们回到入口文件,看看init()方法,app代表的是Vue的实例,现将app存入this.apps中,如果this.app已经存在就返回,如果不是就赋值。this.history是三种的实例对象,然后分情况进行transtionTo()操作,history方法就是给history.cb赋值穿进去的回调函数。
下面看getMatchedComponents(),唯一需要注意的就是我们多次提到的route.matched是路由记录的数据,最终返回的是每个路由记录的components属性值的值。

Router-View

最后讲讲router-view

           

var View = {  name: 'router-view',  functional: true,  props: {    name: {      type: String,      default: 'default'    }  },  render: function render (_, ref) {    var props = ref.props;    var children = ref.children;    var parent = ref.parent;    var data = ref.data;    // 解决嵌套深度问题    data.routerView = true;    var h = parent.$createElement;    var name = props.name;    // route    var route = parent.$route;    // 缓存    var cache = parent._routerViewCache || (parent._routerViewCache = {});    // 组件的嵌套深度    var depth = 0;    // 用于设置class值    var inactive = false;    // 组件的嵌套深度    while (parent && parent._routerRoot !== parent) {      if (parent.$vnode && parent.$vnode.data.routerView) {        depth++;      }      if (parent._inactive) {        inactive = true;      }      parent = parent.$parent;    }    data.routerViewDepth = depth;    if (inactive) {      return h(cache[name], data, children)    }    var matched = route.matched[depth];    if (!matched) {      cache[name] = null;      return h()    }    var component = cache[name] = matched.components[name];    data.registerRouteInstance = function (vm, val) {      // val could be undefined for unregistration      var current = matched.instances[name];      if (        (val && current !== vm) ||        (!val && current === vm)      ) {        matched.instances[name] = val;      }    }    ;(data.hook || (data.hook = {})).prepatch = function (_, vnode) {      matched.instances[name] = vnode.componentInstance;    };    var propsToPass = data.props = resolveProps(route, matched.props && matched.props[name]);    if (propsToPass) {      propsToPass = data.props = extend({}, propsToPass);      var attrs = data.attrs = data.attrs || {};      for (var key in propsToPass) {        if (!component.props || !(key in component.props)) {          attrs[key] = propsToPass[key];          delete propsToPass[key];        }      }    }    return h(component, data, children)  }};

登录后复制

router-view比较简单,functional为true使组件无状态 (没有 data ) 和无实例 (没有 this 上下文)。他们用一个简单的 render 函数返回虚拟节点使他们更容易渲染。props表示接受属性,下面来看看render函数,首先获取数据,然后缓存,_inactive用于处理keep-alive情况,获取路由记录,注册Route实例,h()用于渲染。很简单我也懒得一一再说。

相关推荐:

vue-router的权限控制代码分享

Vue-router结合transition实现app动画切换效果实例分享

三种Vue-Router实现组件间跳转的方法

以上就是vue-router源码实例详解的详细内容,更多请关注【创想鸟】其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至253000106@qq.com举报,一经查实,本站将立刻删除。

发布者:PHP中文网,转转请注明出处:https://www.chuangxiangniao.com/p/2787064.html

(0)
上一篇 2025年3月8日 18:01:03
下一篇 2025年3月8日 18:02:11

AD推荐 黄金广告位招租... 更多推荐

相关推荐

  • 实例详解js中ajax访问

    本文主要和大家介绍原生js中ajax访问的实例详解的相关资料,希望通过本文大家能够掌握理解这部分内容,需要的朋友可以参考下,希望能帮助到大家。 原生js中ajax访问的实例详解 form表单中 登录名: 失去光标即触发事件 function…

    编程技术 2025年3月8日
    200
  • JavaScript事件处理程序详解

    本文主要和大家详细介绍javascript事件处理程序的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能帮助到大家。 nbsp;html>    DOM0级DOM2级 function show() { alert(…

    编程技术 2025年3月8日
    200
  • Nuxt.js Vue服务端渲染详解

    nuxt.js 十分简单易用。一个简单的项目只需将 nuxt 添加为依赖组件即可。本文主要和大家介绍了nuxt.js vue服务端渲染摸索,给大家做个参考,希望能帮助到大家。 Vue因其简单易懂的API、高效的数据绑定和灵活的组件系统,受到…

    编程技术 2025年3月8日
    200
  • Redux和Mobx的选择实例详解

    redux 和 mobx 都是当下比较火热的数据流模型,似乎现在社区里关于该选什么来替代 redux 很自然地成为了一件困惑的事。开发者不确定该选择哪种解决方案。这个问题并不只是出现在 redux 与 mobx 上。无论何时,只要存在选择,…

    编程技术 2025年3月8日
    200
  • jQuery子元素选择器详解

    本文主要和大家详细介绍了jquery选择器之子元素选择器,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能帮助到大家。 nbsp;html>          子元素筛选选择器  :first-child、:last-chil…

    2025年3月8日
    200
  • Vue.js响应式原理详解

    本人是Java背景,许多年前刚接触JavaScript时有点怪怪的,因为它没有 getters 和 setters。随着时间的推移,我开始喜欢上这个缺失的特性,因为相比Java大量的 getter 和 setter,它让代码更简洁。例如,我…

    2025年3月8日
    200
  • javascript中的隐式调用详解

    所谓的隐式调用简单来说就是自动调用一些方法,而这些方法像钩子一样可以在外部修改,从而改变既定行为。 下面我会列举一些最近看到的隐式调用,例子都是点到即止,欢迎补充 数据类型转换 toSting 和 valueOf var obj = { a…

    编程技术 2025年3月8日
    200
  • JavaScript面向对象实例详解

    构造函数和原型对象 构造函数也是函数,用new创建对象时调用的函数,与普通函数的一个区别是,其首字母应该大写。但如果将构造函数当作普通函数调用(缺少new关键字),则应该注意this指向的问题。本文主要和大家介绍了JavaScript面向对…

    2025年3月8日
    200
  • JS属性名加引号与不加引详解

    一般情况下属性名加引号和不加引号是都可以的,效果是一样的。本文主要和大家介绍了js声明对象时属性名加引号与不加引号的问题及解决方法,需要的朋友可以参考下,希望能帮助到大家。 var obj = {   name  : ‘你好’,   ‘ag…

    编程技术 2025年3月8日
    200
  • tween.js缓动补间动画算法详解

    本文主要和大家介绍了tween.js缓动补间动画示例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧,希望能帮助到大家。 一、理解tween.js 如果看到上面的已经理解了,可以跳过下面的部分.下面为对Tween…

    编程技术 2025年3月8日
    200

发表回复

登录后才能评论