My Little World

动态添加路由

背景

需要将动态路由添加到当前静态路由的子路由中
即在路由的children属性中添加新路由

原理

使用router.addRoutes方法API进行添加

解决办法

在beforeEach方法中请求动态路由进行添加,
通过设置flag,保证动态路由仅请求一次

问题

直接增加同名路由无法覆盖

1
2
3
4
5
6
7
8
9
10
const routes = [
{ path: '/foo',name:'foo', component: Foo,children:[{ path: '/bar2/:id?',name:'bar2', component: Foo1 }]}
]
const routes1 = [
{ path: '/foo',name:'foo', component: Foo ,children:[{ path: '/bar21/:id?',name:'bar21', component: Foo2 }]},
]
const router = new VueRouter({
routes
})
router.addRoutes(routes1)

添加路由记录时,如果之前已经添加过同名路由,则只会警告,不会更新
所以如果想通过传入不同的children进行子路由更新无法实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
function addRoutes (routes) {
createRouteMap(routes, pathList, pathMap, nameMap);
}

routes.forEach(function (route) {
addRouteRecord(pathList, pathMap, nameMap, route);
});

function addRouteRecord ( pathList, pathMap, nameMap ) {
//如果有子路由,递归添加到路由表中
if (route.children) {
route.children.forEach(function (child) {
var childMatchAs = matchAs
? cleanPath((matchAs + "/" + (child.path)))
: undefined;
console.log(childMatchAs)
addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs);
});
}

if (!pathMap[record.path]) { 关键!!!!!!详见下文
pathList.push(record.path);
pathMap[record.path] = record;
}

if (name) {
if (!nameMap[name]) {
nameMap[name] = record;
} else if ( !matchAs) { //直接警告,不更新
warn(
false,
"Duplicate named routes definition: " +
"{ name: \"" + name + "\", path: \"" + (record.path) + "\" }"
);
}
}
}

addRoutes时,子路由作为children属性,依托父路由添加时,子路由被递归添加后,
但父路由因为之前已经添加过,所以不会再对父路由进行任何处理,新添加的子路由
不会跟随父路由更新到路由表中,即addRoutes过程不会对原来老路由产生任何变动
所以新的子路由也不会被添加进去

解决办法一

拼接好路由后,通过重新生成matcher对象,清空原始路由信息,再将最终路由添加进去
详见
缺点:容易引发各种问题,且引起重复路由

解决办法二

按照API规则添加路由

1
2
router.addRoutes(routes: Array<RouteConfig>)
动态添加更多的路由规则。参数必须是一个符合 routes 选项要求的数组。

将待更新的路由对象抽离,拿到动态路由拼接好后,与404路由组成数组,直接添加路由
详见

无法正常刷新

F12刷新浏览器页面后路由对象name为null,无法正常跳转
如果直接再在next函数中添加跳转信息会引起无线循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
History.prototype.confirmTransition = function confirmTransition (route, onComplete, onAbort) {
var queue = [].concat(
// in-component leave guards
extractLeaveGuards(deactivated),
// global before hooks
this.router.beforeHooks,
// in-component update hooks
extractUpdateHooks(updated),
// in-config enter guards
activated.map(function (m) { return m.beforeEnter; }),
// async components
resolveAsyncComponents(activated)
);

this.pending = route;
var iterator = function (hook, next) {
if (this$1.pending !== route) {
return abort()
}
try {
hook(route, current, function (to) {
if (to === false || isError(to)) {
// next(false) -> abort navigation, ensure current URL
this$1.ensureURL(true);
abort(to);
} else if (
typeof to === 'string' ||
(typeof to === 'object' &&
(typeof to.path === 'string' || typeof to.name === 'string'))
) {
// next('/') or next({ path: '/' }) -> redirect
abort();
if (typeof to === 'object' && to.replace) {
this$1.replace(to);
} else {
this$1.push(to); //引起递归调用
}
} else {
// confirm transition and pass on the value
next(to);
}
});
} catch (e) {
abort(e);
}
};
}
History.prototype.push = function push (location, onComplete, onAbort) {
var this$1 = this;

var ref = this;
var fromRoute = ref.current;
this.transitionTo(location, function (route) {
pushState(cleanPath(this$1.base + route.fullPath));
handleScroll(this$1.router, route, fromRoute, false);
onComplete && onComplete(route);
}, onAbort);
};
History.prototype.transitionTo = function transitionTo (
location,
onComplete,
onAbort
) {
var this$1 = this;
var route = this.router.match(location, this.current);
this.confirmTransition(...)
}

在next具体路由后再调用next(),可实现中止导航,即暂停递归

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
if(!hasMenus){
hasMenus = true
let temp = await createRoutes() //拿到数据
router.addRoutes(temp) //添加路由
if(!to.name){ //处理刷新,刷新时,当前页面name会变成null
next({name:to.path.substring(1)})
}
}
if (to.matched.some(res => res.meta.requireAuth)) {// 判断是否需要登录权限
cookies.getAuthorization().then(token=>{
if(!token){
next({
name: 'login',
params: {from: to.name}
})
}else{
next()
}
})
} else {
next() //终止递归
}

小结

多读源码总是用好处的