亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定

前端入門之(vue-router全解析三)

標簽:
Vue.js

我们在源码中找到router-link组件的代码:

var Link = {  name: 'router-link',  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;    // Support global empty 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 {      // find the first <a> child and apply listener and href
      var a = findAnchor(this.$slots.default);      if (a) {        // in case the <a> is a static node
        a.isStatic = false;        var extend = _Vue.util.extend;        var aData = a.data = extend({}, a.data);
        aData.on = on;        var aAttrs = a.data.attrs = extend({}, a.data.attrs);
        aAttrs.href = href;
      } else {        // doesn't have <a> child, apply listener to self
        data.on = on;
      }
    }    return h(this.tag, data, this.$slots.default)
  }
};function guardEvent (e) {  // don't redirect with control keys
  if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) { return }  // don't redirect when preventDefault called
  if (e.defaultPrevented) { return }  // don't redirect on right click
  if (e.button !== undefined && e.button !== 0) { return }  // don't redirect if `target="_blank"`
  if (e.currentTarget && e.currentTarget.getAttribute) {    var target = e.currentTarget.getAttribute('target');    if (/\b_blank\b/i.test(target)) { return }
  }  // this may be a Weex event which doesn't have this method
  if (e.preventDefault) {
    e.preventDefault();
  }  return true}

代码不是很多,我们直接结合demo展示下router-view跟router-link组件,先简单看一下我们要实现的需求:


webp

20180921180518150.png

可以看到,页面就两个tab按钮,然后点击每个tab按钮的时候切换不同的页面内容.

我们首先创建两个页面a页面跟b页面:

page-a.vue:

<template>
  <div id="page-a-container">
    我是a页面  </div></template><script></script><style scoped>
  #page-a-container{    background-color: red;    color: white;    font-size: 24px;    height: 100%;
  }</style>

page-b.vue:

<template>
  <div id="page-b-container">
    我是b页面  </div></template><script></script><style scoped>
  #page-b-container{    background-color: yellow;    color: black;    font-size: 24px;    height: 100%;
  }</style>

然后是我们的router.js文件:

export default new Router({  mode:'hash',
  routes: [
    {
      path: '/a',
      name: 'pageA',
      component: pageA
    },
    {      path: '/b',
      name: 'pageB',
      component: pageB
    },
  ]
})

最后是我们的App.vue文件:

<template>
  <div id="app">
    <div class="tab-container">
      <router-link class="tab" :to="{name:'pageA'}">tab1</router-link>
      <router-link class="tab" :to="{name:'pageB'}">tab2</router-link>
    </div>
    <router-view class="router-view"/>
  </div></template><script>
  export default {    name: 'App',
    created() {      console.log('app', this)
    }
  }</script><style>
  body, html {    width: 100%;    overflow: auto;    height: 100%;
  }

  * {    margin: 0px;    padding: 0px;
  }  #app {    font-family: 'Avenir', Helvetica, Arial, sans-serif;    -webkit-font-smoothing: antialiased;    -moz-osx-font-smoothing: grayscale;    text-align: center;    color: #2c3e50;    position: relative;    height: 100%;
  }  .tab-container {    background-color: #efefef;    height: 50px;    display: flex;    box-sizing: border-box;    padding: 10px;
  }  .tab{    color: black;    font-size: 24px;    flex: 1;    text-decoration: none;
  }  .router-link-exact-active{    color: red;    font-size: 24px;
  }  .router-view{    position: absolute;    top: 50px;    bottom: 0px;    left: 0px;    height: auto;    width: 100%;
  }</style>

代码比较简单,我就直接上代码了,然后我们运行代码:


webp

20180921184724383.gif

好啦,简单的几行代码就可以玩起来了,我们来分析一下router-link组件:

var Link = {  name: 'router-link',  props: {    to: {      type: toTypes, //可以传递string 类型,比如我们demo的a页面"/a"  或者是object :to="{name:'pageA'}"
      required: true //必须传递的属性
    },    tag: {      type: String, //渲染的标签类型,默认是a标签
      default: 'a'
    },    exact: Boolean,    append: Boolean,    replace: Boolean,    activeClass: String, //激活状态的class
    exactActiveClass: String, //精确对比情况下的激活状态的class
    event: {      type: eventTypes, //要选择触发路由操作的事件
      default: 'click'
    }
  },  render: function render (h) {    var this$1 = this;    var router = this.$router;    var current = this.$route; //当前route
    var ref = router.resolve(this.to, current, this.append);//解析当前route路由
    var location = ref.location;//获取当前路由的location
    var route = ref.route;    var href = ref.href;    var classes = {};    var globalActiveClass = router.options.linkActiveClass; //全局的激活状态class
    var globalExactActiveClass = router.options.linkExactActiveClass; //精确对比情况下全局的激活状态class
    // Support global empty 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 {      // find the first <a> child and apply listener and href
      var a = findAnchor(this.$slots.default);      if (a) {        // in case the <a> is a static node
        a.isStatic = false;        var extend = _Vue.util.extend;        var aData = a.data = extend({}, a.data);
        aData.on = on;        var aAttrs = a.data.attrs = extend({}, a.data.attrs);
        aAttrs.href = href;
      } else {        // doesn't have <a> child, apply listener to self
        data.on = on;
      }
    }    return h(this.tag, data, this.$slots.default)
  }
};

在render函数中,通过引用当前route对象,根据当前route信息改变改变当前组件的class(激活class、默认class),然后监听组件的事件进行路由跳转.

我们下面跟着官网的节奏结合demo往下走哈,当然! 童鞋们也可以直接去看官网...

动态路由匹配
我们需求是:当访问的是“/page/pageA”或者是“/page/pageB”我用一个公用的组件当page页面,然后匹配page后面的“pageA”和“pageB”做网络请求,请求对应的页面数据,最后渲染在page组件中.
首先我们创建一个页面叫page.vue,然后把page.vue放到router.js中去:

page.vue文件:

<template>
  <div id="page-container">
    {{pageDesc}}  </div></template><script>
  export default {    name: 'page',
    data(){      return{        pageDesc:''
      }
    },
    mounted(){      this.fetchData();
    },    methods:{
      fetchData(){        this.pageDesc=`我是${this.$route.params.pageId}页面`
      }
    }
  }</script><style scoped>
  #page-container{    background-color: red;    color: white;    font-size: 24px;    height: 100%;
  }</style>

router.js中把我们的page页面放进去,并且添加pageId字段用来匹配作参数.

import Vue from 'vue'import Router from 'vue-router'import page from '@/components/page'Vue.use(Router)export default new Router({  mode:'hash',  routes: [

    {      path:'/page/:pageId',      name:'page',      component:page
    }
  ]
})

然后当我们访问http://localhost:8080/#/page/pageHome的时候:

webp

20180926205725143.png


当我们把pageHome改成pageB的时候,我们在浏览器按下回车,或者执行

this.$router.push({path:'/page/pageB'})

的时候,我们会发现页面并没有改变


webp

20180926205946563.png

在官网有段话:
当使用路由参数时,例如从 /user/foo 导航到 /user/bar,原来的组件实例会被复用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会再被调用。

所以我们这里也是一样的~~~

我们第一次进入页面的时候,我们在页面的mounted生命周期方法中去请求数据:

 mounted(){      this.fetchData();
    },    methods:{
      fetchData(){        this.pageDesc=`我是${this.$route.params.pageId}页面`
      }
    }

当我们切换链接的时候,或者执行下面代码的时候

this.$router.push({path:'/page/pageB'})

我们page.vue中会接受到监听,我们可以监听$route变量的变化,然后重新请求数据:

watch:{      '$route'(to,from){        this.fetchData();
      }
    },
    mounted(){      this.fetchData();
    },    methods:{
      fetchData(){        this.pageDesc=`我是${this.$route.params.pageId}页面`
      }
    }

或者用beforeRouteUpdate回调:

 beforeRouteUpdate (to, from, next) {
      next();      this.fetchData();
    },

这两个方法还是有点区别的,首先beforeRouteUpdate是2.2版本中引入的,然后beforeRouteUpdate从字面意思就可以知道,它是在路由变化之前调用的,而监听$route变化是在route已经改变后回调的.

监听$route变化的原理我就不解释了,我们去源码中看一下beforeRouteUpdate的调用时间:

History.prototype.confirmTransition = function confirmTransition (route, onComplete, onAbort) {
  ...  var queue = [].concat(
   ...
    extractUpdateHooks(updated),
  ...
  );
....
};

confirmTransition方法我们前面一节分析过,它是在route变化之前对route做的一些列操作的一个方法,感兴趣的小伙伴可以去看我们之前的两篇文章.

好啦!! 有了这两个方法,我们就可以在这两个方法中监听route的变化,然后作网路请求,最后显示数据了,效果我就不演示了哈,小伙伴自己去跑跑代码就知道了~

当我们使用router的push方法去打开这个页面的时候:

const pageId = 123router.push({ name: 'page', params: { pageId }}) // -> /page/123

webp

20180926212813104.png

然后我们还可以使用path匹配:

router.push({ path: `/page/${userId}` }) // -> /page/123

跟上面的效果一样,我就不截图啦~~

最后当我们执行

const pageId = 123
      this.$router.push({ path: '/page', params: { pageId }}) // -> 无效

webp

20180926213046205.png

可以看到,我们页面出现空白,因为我们没有router-view没有匹配到路由.所以无效.

路由组件传参
我们当前页面拿到参数的方式为:

 methods:{
      fetchData(){        console.log('fetchData',this.$route);        this.pageDesc=`我是${this.$route.params.pageId}页面`
      }
    }

我们把用this.$route.params.pageId方式改为this.pageId:

 methods:{
      fetchData(){        this.pageDesc=`我是${this.pageId}页面`
      }
    }
<template>
  <div id="page-container">
    {{pageDesc}}  </div></template><script>
  export default {    name: 'page',    props:['pageId'],
    data(){      return{        pageDesc:''
      }
    },    watch:{
      pageId(val,oldVal){        if(val!==oldVal){          this.fetchData();
        }
      }
    },
    mounted(){      this.fetchData();
    },    methods:{
      fetchData(){        this.pageDesc=`我是${this.pageId}页面`
      }
    }
  }</script><style scoped>
  #page-container{    background-color: red;    color: white;    font-size: 24px;    height: 100%;
  }</style>

我们直接把params的pageId直接映射到page.vue的props中了,所以我们可以在页面使用this.pageId,然后通过监听pageId的变化最后做网络请求,渲染页面数据.

当然,在router.js中我们还需要设置page页面的props为true:

export default new Router({  mode:'hash',  routes: [
    {      path:'/page/:pageId',      name:'page',      component:page,      props:true
    }
  ]
})

props除了boolean类型外,还可以设置为“对象模式”跟“函数模式”,

{      path:'/page/:pageId',
      name:'page',
      component:page,
      props:{pageId:123123}
    }
   {      path:'/page/:pageId',
      name:'page',
      component:page,
      props:(route)=>{
        return route.params;
      }
    }

我们可以对应找到router-view的源码:

var View = {
  name: 'router-view',
  functional: true,
  props: {
    name: {
      type: String,      default: 'default'
    }
  },
  render: function render (_, ref) {
    ....    // resolve props
    var propsToPass = data.props = resolveProps(route, matched.props && matched.props[name]);
   ...    return h(component, data, children)
  }
};
function resolveProps (route, config) {  switch (typeof config) {    case 'undefined':      return
    case 'object':      return config    case 'function':      return config(route)    case 'boolean':      return config ? route.params : undefined
    default:      if (process.env.NODE_ENV !== 'production') {
        warn(          false,          "props in \"" + (route.path) + "\" is a " + (typeof config) + ", " +          "expecting an object, function or boolean."
        );
      }
  }
}

如果我们指定了props:true,然后page.vue文件中又没指定props的时候,router-view会把params当属性绑定在vm的el上:

{      path:'/page/:pageId',
      name:'page',
      component:page,
      props:true
    }

webp

20180926220651316.png

可以看到,我们的id为“page-container”的标签上有一个pageid属性.

导航守卫

在我们前面文章中有介绍,在vue-router的源码中,我们看到了很多router操作route时的一些回调:

VueRouter.prototype.beforeEach = function beforeEach (fn) {  return registerHook(this.beforeHooks, fn)
};

VueRouter.prototype.beforeResolve = function beforeResolve (fn) {  return registerHook(this.resolveHooks, fn)
};

VueRouter.prototype.afterEach = function afterEach (fn) {  return registerHook(this.afterHooks, fn)
};

VueRouter.prototype.onReady = function onReady (cb, errorCb) {  this.history.onReady(cb, errorCb);
};

VueRouter.prototype.onError = function onError (errorCb) {  this.history.onError(errorCb);
};

我们一个一个来认识一下,首先是全局前置守卫,既然是router的方法,所以我们需要拿到router实例,然后调用router的beforeEach方法注册一个回调,我们就直接在App.vue中操作了:

<script>
  export default {    name: 'App',
    created() {      this.$router.beforeEach((to, from, next) => {        if(to.name==='page'&&to.params.pageId==='123'){
          next({path:'/a'});          return;
        }
        next();
      })
    }
  }</script>

router.js:

import Vue from 'vue'import Router from 'vue-router'import pageA from '@/components/page-a'import pageB from '@/components/page-b'import page from '@/components/page'Vue.use(Router)export default new Router({  mode:'hash',  routes: [
    {      path: '/a',      name: 'pageA',      component: pageA,      props: true
    },
    {      path: '/b',      name: 'pageB',      component: pageB
    },
    {      path:'/page/:pageId',      name:'page',      component:page,      props:true
    }
  ]
})



作者:vv_小虫虫
链接:https://www.jianshu.com/p/d63f6c058205


點擊查看更多內容
TA 點贊

若覺得本文不錯,就分享一下吧!

評論

作者其他優質文章

正在加載中
  • 推薦
  • 評論
  • 收藏
  • 共同學習,寫下你的評論
感謝您的支持,我會繼續努力的~
掃碼打賞,你說多少就多少
贊賞金額會直接到老師賬戶
支付方式
打開微信掃一掃,即可進行掃碼打賞哦
今天注冊有機會得

100積分直接送

付費專欄免費學

大額優惠券免費領

立即參與 放棄機會
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號

舉報

0/150
提交
取消