我们在源码中找到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组件,先简单看一下我们要实现的需求:
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>
代码比较简单,我就直接上代码了,然后我们运行代码:
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的时候:
20180926205725143.png
当我们把pageHome改成pageB的时候,我们在浏览器按下回车,或者执行
this.$router.push({path:'/page/pageB'})
的时候,我们会发现页面并没有改变
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
20180926212813104.png
然后我们还可以使用path匹配:
router.push({ path: `/page/${userId}` }) // -> /page/123
跟上面的效果一样,我就不截图啦~~
最后当我们执行
const pageId = 123 this.$router.push({ path: '/page', params: { pageId }}) // -> 无效
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 }
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
共同學習,寫下你的評論
評論加載中...
作者其他優質文章