vue-router的实现原理_vuerouter 原理及实现

作者:June.1

https://blog.csdn.net/qq_39043923/article/details/107063384

路由模式

  • hash:默认hash 模式, 使用 URL hash值来作路由。

  • history:依赖 HTML5 History API 和服务器配置。

  • abstract:支持所有 JavaScript 运行环境,如 Node.js 服务器端。

    ips hash 和 history 中都会记录浏览历史,保存在浏览器的路由栈中

模式分配

/* other... */if (!inBrowser) { // 非浏览器模式 mode = 'abstract' } this.mode = 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}`)     } } /* other... */

hash 模式原理

window.location对象 例如:https://www.baidu.com:443/test#/params

hash :设置或返回从 (#) 开始的 URL(锚)。#/params

host :设置或返回主机名和当前 URL 的端口号 www.baidu.com:443。

hostname:设置或返回当前 URL 的主机名www.baidu.com。

href :设置或返回完整的 URL。https://www.baidu.com:443/test#/params

pathname:设置或返回当前 URL 的路径部分。/test

port:设置或返回当前 URL 的端口号。443

search :设置或返回从问号 (?) 开始的 URL(查询部分)。

assign() :加载新的文档。

reload() :重新加载当前文档。

replace() :用新的文档替换当前文档。

  • hash即浏览器url中 #后面的内容。

  • 通过 window.location.hash 来获取内容

765a3c1708ffd03cbba4c59d280ba382.png
  • www.baidu.com/这里是什么内容都是忽略的ssss#内容 详细如图。

1b93ec608e32f5be2a6a5980d17e5e01.png
  • 通过 hashchange 事件来监听浏览器的 hash值的改变, 渲染响应路由页面。

if('onhashchange' in window) { window.addEventListener('hashchange',function(e){   console.log(window.location.hash) },false)}

history 模式原理

history 对象方法

  • go() :接受一个整数为参数,移动到该整数指定的页面,比如history.go(1)相当于history.forward(),history.go(-1)相当于history.back(),history.go(0)相当于刷新当前页面。

  • back() :移动到上一个访问页面,等同于浏览器的后退键,常见的返回上一页就可以用back(),是从浏览器缓存中加载,而不是重新要求服务器发送新的网页。

  • forward() :移动到下一个访问页面,等同于浏览器的前进键。

  • pushState():

history.pushstate(state,title,url)。

state: 一个与指定网址相关的状态对象,popState事件触发时,该对象会传入回调函数,如果不需要这个对象,此处可填null。

title: 新页面的标题,但是所有浏览器目前都忽略这个值,因此这里可以填null。

url: 新的网址,必须与当前页面处在同一个域,浏览器的地址栏将显示这个网址。

  • replaceState(): history.replaceState()方法的参数和pushState()方法一摸一样,区别是它修改浏览器历史当中的记录。

  • length:history.length属性保存着历史记录的url数量,初始时该值为1,如果当前窗口先后访问了三个网址,那么history对象就包括3项,history.length=3。

    state:返回当前页面的state对象。可以通过replaceState()和pushState()改变state,可以存储很多数据

scrollRestoration
history.scrollRestoration = 'manual'; 关闭浏览器自动滚动行为history.scrollRestoration = 'auto'; 打开浏览器自动滚动行为(默认)

  • window.location.href.replace(window.location.origin, '') 获取记录内容。

68533dc0007709141b22a92d20e7cec0.png
  • 通过popstate事件来监听history模式, 渲染响应路由页面。

popState 事件

 每当同一个文档的浏览历史(即history)出现变化时,就会触发popState事件

注意:仅仅调用pushState方法或replaceState方法,并不会触发该事件,只有用户点击浏览器后退和前进按钮时,或者使用js调用back、forward、go方法时才会触发。另外该事件只针对同一个文档,如果浏览历史的切换,导致加载不同的文档,该事件不会被触发。

使用的时候,可以为popState事件指定回调函数

window.onpopstate = function (event) {  console.log('location: ' + document.location);  console.log('state: ' + JSON.stringify(event.state));};// 或者window.addEventListener('popstate', function(event) {  console.log('location: ' + document.location);  console.log('state: ' + JSON.stringify(event.state));});

    回调函数的参数是一个event事件对象,它的 state 属性指向 pushState 和 replaceState 方法为当前url所提供的状态对象(即这两个方法的第一个参数)。上边代码中的event.state就是通过pushState和replaceState方法为当前url绑定的state对象。

    这个state也可以直接通过history对象读取 history.state。

    注意:页面第一次加载的时候,浏览器不会触发popState事件

 window.addEventListener('popstate', e => {    console.log(window.location.href.replace(window.location.origin, ''));})

完整的DEMO

    "UTF-8">        "viewport" content=    "X-UA-Compatible" content=    原生实现hash和browser两种路由模式        
           "/home" replace="true">主页               "/news">新闻               "/team">团队               "/about">关于               "/abcd">随便什么       
       
"router-view">
         

route-link

    该组件支持用户在具有路由功能的应用中(点击)导航,默认渲染成带有正确链接的标签,可以通过tag属性生成别的标签。

    它本质上是通过在生成的标签上绑定了click事件,然后执行对应的VueRouter实例的push()实现的,对于router-link组件来说,可以传入以下props:

  • to 表示目标路由的链接,当被点击后,内部会立刻把to的值传到router.push(),所以这个值可以是一个字符串或者是描述目标位置的对象。

  • tag router-link组件渲染的标签名,默认为a。

  • exact 布尔类型,“是否激活”默认类名的依据是包含匹配。

  • append 布尔类型,设置append属性后,则在当前(相对)路劲前添加基路径。

  • replace 布尔类型,设置replace后,当点击时会调用router.replace()而不是router.push(),这样导航后不会留下history记录。

  • activeClass 链接激活时使用的CSS类名。

  • exactActiveClass 配置当链接被精确匹配的时候应该激活的 class。

  • event 声明可以用来触发导航的事件。可以是一个字符串或是一个包含字符串的数组。

var Link = {  name: 'RouterLink',  props: {    to: {      type: toTypes,      required: true    },    tag: {      type: String,      default: 'a'    },    exact: Boolean,    append: Boolean,    replace: Boolean,    activeClass: String,    exactActiveClass: String,    event: {      type: eventTypes,      default: 'click'    }  },  render: function render (h) {    var this$1 = this;    var router = this.$router;    var current = this.$route;    var ref = router.resolve(this.to, current, this.append);    var location = ref.location;    var route = ref.route;    var href = ref.href;    var classes = {};    var globalActiveClass = router.options.linkActiveClass;    var globalExactActiveClass = router.options.linkExactActiveClass;    // 支持全局空 Active Class    var activeClassFallback = globalActiveClass == null      ? 'router-link-active'      : globalActiveClass;    var exactActiveClassFallback = globalExactActiveClass == null      ? 'router-link-exact-active'      : globalExactActiveClass;    var activeClass = this.activeClass == null      ? activeClassFallback      : this.activeClass;    var exactActiveClass = this.exactActiveClass == null      ? exactActiveClassFallback      : this.exactActiveClass;    var compareTarget = location.path      ? createRoute(null, location, null, router)      : route;    classes[exactActiveClass] = isSameRoute(current, compareTarget);    classes[activeClass] = this.exact      ? classes[exactActiveClass]      : isIncludedRoute(current, compareTarget);    var handler = function (e) {      if (guardEvent(e)) {        if (this$1.replace) {          router.replace(location);        } else {          router.push(location);        }      }    };    var on = { click: guardEvent };    if (Array.isArray(this.event)) {      this.event.forEach(function (e) { on[e] = handler; });    } else {      on[this.event] = handler;    }    var data = {      class: classes    };    if (this.tag === 'a') {      data.on = on;      data.attrs = { href: href };    } else {      // 找到第一个< a >子级,并应用侦听器和href      var a = findAnchor(this.$slots.default);      if (a) {        // 如果< a >是静态节点        a.isStatic = false;        var aData = a.data = extend({}, a.data);        aData.on = on;        var aAttrs = a.data.attrs = extend({}, a.data.attrs);        aAttrs.href = href;      } else {        // 没有< a >子代,将侦听器应用于自身        data.on = on;      }    }    return h(this.tag, data, this.$slots.default)  }}

route-view

 router-view是一个 functional 组件,渲染路径匹配到的视图组件。 渲染的组件还可以内嵌自己的 ,根据嵌套路径,渲染嵌套组件。

    它只有一个名为name的props,这个name还有个默认值,就是default,一般情况下,我们不用传递name,只有在命名视图的情况下,我们需要传递name,命名视图就是在同级展示多个视图,而不是嵌套的展示出来。

router-view组件渲染时是从VueRouter实例._route.matched属性获取需要渲染的组件,也就是我们在vue内部的this.$route.matched上获取的

var View = {  name: 'RouterView',  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;    // 由devtools用来显示路由器视图标记    data.routerView = true;    // 直接使用父上下文的createElement()函数    // 以便由router-view呈现的组件能够解析命名的插槽    var h = parent.$createElement;    var name = props.name;    var route = parent.$route;    var cache = parent._routerViewCache || (parent._routerViewCache = {});    // 确定当前视图深度,同时检查树是否    // 已被切换为非活动但保持活动状态    var depth = 0;    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可能未定义      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;    };    // 解析 props    var propsToPass = data.props = resolveProps(route, matched.props && matched.props[name]);    if (propsToPass) {      // clone to prevent mutation      propsToPass = data.props = extend({}, propsToPass);      // 将未声明的props具作为属性传递      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)  }}
a099641c6da8c77709b0c4df67bc8901.png

更多前端分享,请关注:

前端路人甲

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值