我们在源码中找到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/12320180926212813104.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
共同學習,寫下你的評論
評論加載中...
作者其他優質文章






