上一节前端入门之(vue-router全解析二)我们带着vue-router的push方法走了一遍源码,然后还分析了router-view的源码,最后还差router-link组件没有分析了,我们今天继续vue-router解析.
我们在源码中找到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 child and apply listener and href
var a = findAnchor(this.$slots.default);
if (a) {
// in case the 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 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组件,先简单看一下我们要实现的需求:
可以看到,页面就两个tab按钮,然后点击每个tab按钮的时候切换不同的页面内容.
我们首先创建两个页面a页面跟b页面:
page-a.vue:
我是a页面
page-b.vue:
我是b页面
然后是我们的router.js文件:
export default new Router({
mode:'hash',
routes: [
{
path: '/a',
name: 'pageA',
component: pageA
},
{
path: '/b',
name: 'pageB',
component: pageB
},
]
})
最后是我们的App.vue文件:
tab1
tab2
好啦,简单的几行代码就可以玩起来了,我们来分析一下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 child and apply listener and href
var a = findAnchor(this.$slots.default);
if (a) {
// in case the 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 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文件:
{{pageDesc}}
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的时候:
当我们把pageHome改成pageB的时候,我们在浏览器按下回车,或者执行
this.$router.push({path:'/page/pageB'})
在官网有段话:
当使用路由参数时,例如从 /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 = 123
router.push({ name: 'page', params: { pageId }}) // -> /page/123
然后我们还可以使用path匹配:
router.push({ path: `/page/${userId}` }) // -> /page/123
跟上面的效果一样,我就不截图啦~~
最后当我们执行
const pageId = 123
this.$router.push({ path: '/page', params: { pageId }}) // -> 无效
可以看到,我们页面出现空白,因为我们没有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}页面`
}
}
{{pageDesc}}
我们直接把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
}
可以看到,我们的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中操作了:
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
}
]
})
现在的逻辑是,当判断访问的是"/page/123"的时候,直接链接到“/a”路径也就是我们的pageA页面:
好啦,看完我们的全局前置守卫,我们来看一下其它的几个:
export default {
name: 'App',
created() {
this.$router.beforeEach((to, from, next) => {
console.log('beforeEach');
if(to.name==='page'&&to.params.pageId==='123'){
next({path:'/a'});
return;
}
next();
});
this.$router.beforeResolve((to,from,next)=>{
console.log('beforeResolve');
next();
});
this.$router.afterEach((to,from)=>{
console.log('afterEach');
});
this.$router.onReady(()=>{
console.log('onReady');
});
this.$router.onError((erro)=>{
console.log('onError',erro);
});
}
我们访问一下“http://localhost:8080/#/a”
可以看到,当vue-router就位的时候,会调用onReady方法,整个onReady方法只会被调用一次:
History.prototype.transitionTo = function transitionTo (location, onComplete, onAbort) {
...
this.confirmTransition(route, function () {
// fire ready cbs once
if (!this$1.ready) {
this$1.ready = true;
this$1.readyCbs.forEach(function (cb) { cb(route); });
}
}, function (err) {
...
});
};
具体的用法啥的我就不带着看了哈,小伙伴自己跑跑看看log就可以了.
好啦,我们现在有一个这样的需求,当我们访问a页面的时候,我们需要强制用户登录,然后才能进入a页面
我们首先创建一个叫login.vue的文件:
代码很简单,就一个登录按钮,然后点击登录按钮模拟一下请求网络接口登录成功,然后跳转到从上个页面传递过来的路径.
然后在router.js中注册一下login页面,并且给a和b页面设置一个路由元信息:
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'
import login from '@/components/login'
Vue.use(Router)
export default new Router({
mode:'hash',
routes: [
{
path: '/a',
name: 'pageA',
component: pageA,
props: true,
meta:{requiresAuth: true}
},
{
path: '/b',
name: 'pageB',
component: pageB,
meta:{requiresAuth: true}
},
{
path:'/page/:pageId',
name:'page',
component:page,
props:true
},
{
path:'/login',
name:'login',
component:login
}
]
})
可以看到,当我们访问/a路径的时候,我们会走:
this.$router.beforeEach((to, from, next) => {
console.log('beforeEach');
if (to.meta && !!to.meta.requiresAuth&&!this.$router.loggedIn) {
next({name: 'login',params:{redirect: to.fullPath}});
return;
}
next();
});
代码,然后由next({name: ‘login’,params:{redirect: to.fullPath}});链接到了login页面,最后login页面通过传递的redirect地址又重新打开了a页面:
login() {
alert('登录成功')
this.$router.loggedIn = true;
this.$router.push({path: this.$route.params.redirect});
}
好啦,我们已经跟这vue-router的官网把大部分的api走了一遍,还有一些api就不一一解析了,小伙伴自己去试试啊,最主要的是要结合demo自己跑一遍,光看是没有用的,要多练~~~
本篇有点长哈,感谢小伙伴的陪伴,不早啦,睡觉啦~~
欢迎入群,欢迎交流~~
qq群链接:
参考链接:
vue-router官网: https://router.vuejs.org/zh/