vue-router 4.0 动态路由会跳转到 404 页面的问题

引子

开发过前端单页面应用的小伙伴们,应该对前端路由都不陌生吧。

无论是用 vue 或者 react,都有官方提供的 router 方案。

但是有些场景下,处于安全性和友好性考虑,我们需要用到动态路由。

如果你不知道什么叫动态路由,可以想一想下面的场景。

比如现在我们的项目中有一些页面,只有 vip 用户能看到,普通用户不能看到,那么我们怎么做呢?

我们首先的做法肯定是,将该页面的入口隐藏掉。但是如果仅仅只是这样做,还不够,那只是假隐藏,因为用户仍然可以通过直接输入链接的方式来访问到该页面。

正确的做法应该是:首先隐藏掉入口;其次根据用户的权限,动态的生成对应的路由

这篇文章主要是想分享下,最近使用 vue-router 4.0 版本过程中,由于用到了动态路由引起的一些问题,以及个人研究出来的解决方案。

为了清楚的描述我们的问题,现在假设我们有两个这样的页面:

image.png

那么我们就会对应着两个不同的路由:

[
    { path: '/home', name: 'home', component: Home },
    { path: '/vip', name: 'vip', component: Vip }
]

当然为了简化业务模型,这里,我们只用了两个路由。分别对应着两个页面,一个是所有用户都能看到的主页 home;一个是登录了的用户才能看到的 vip 页面。

当然在实际的业务逻辑中,肯定是需要登录、注册等页面,这里假设这些操作我们都能在 home 页面内完成。

如果不需要根据用户来区分的话,我们直接像下面这样生成对应的路由即可:

const router = createRouter({
    history: createWebHistory(import.meta.env.BASE_URL),
    scrollBehavior: () => ({ y: 0 }),
    routes: [
        // 重定向到 /home 路径
        { path: '/', redirect: "/home", },
        { path: '/home', name: 'home', component: Home },
        { path: '/vip', name: 'vip', component: Vip },
        // 404 页面
        {
            path: "/404",
            name: "NotFound",
            component: () => import("@views/404.vue"),
            hidden: true,
            meta: { title: "404" },
        },
        // 当什么都没有匹配到的时候,重定向页面到 404 页面
        { path: "/:pathMatch(.*)", redirect: "/404", name: "notMatch", hidden: true },
    ],
})

但是如果现在的需求是需要我们来动态的区分,我们应该怎么做呢?

动态路由

我们先来了解下,如何实现动态路由。

如果你没有用过 vue-router 的动态路由,可以先参考下 vue-router 的官方文档:

Dynamic Routing | Vue Router

根据文档里所写,想要动态的添加、删除路由,直接通过下面的方式即可:

// 增加 route
router.addRoute({ path: '/home', name: 'home', component: Home })

// 删除 route
router.removeRoute('home')

初窥门径

所以,我们可以稍微改造下我们之前的代码:

// router.js
const router = createRouter({
    history: createWebHistory(import.meta.env.BASE_URL),
    scrollBehavior: () => ({ y: 0 }),
    routes: [
        // 重定向到 /home 路径
        { path: '/', redirect: "/home", },
        { path: '/home', name: 'home', component: Home },
        // 404 页面
        {
            path: "/404",
            name: "NotFound",
            component: () => import("@views/404.vue"),
            hidden: true,
            meta: { title: "404" },
        },
        // 当什么都没有匹配到的时候,重定向页面到 404 页面
        { path: "/:pathMatch(.*)", redirect: "/404", name: "notMatch", hidden: true },
    ],
})

export default router;

// api.js
export const login = async () => {
    // 执行登录操作
    let res = await dologin();

    if (res.sucess) {
        await storeUserInfo(res);
        // 登录成功,动态的添加 vip 路由
        router.addRoute({ path: '/vip', name: 'vip', component: Vip });
    }
}

// permission.js
router.beforeEach(async (to, from, next) => {
    // 如果存在用户信息
    if (await getUserInfo()) {
        // 如果 vip 这个 router 没有初始化,那么动态的增加 vip 路由
        if (!router.hasRoute("vip")) {
            router.addRoute({ path: '/vip', name: 'vip', component: Vip });
        }
    }
    next();
});

router.afterEach((to) => {
  // 在导航被确认后,调用 afterEach 钩子,执行一些操作
  doSomething();
});

经过改造之后,我们在初始化的时候,没有加入 vip 路由,那么你即使直接通过 https://example.com/vip 链接也无法访问到该页面,会被最后的 notMatch 路由匹配到,最后被重定向到 404 页面。

关于 noMatch 路由的用法,不了解的小伙伴,可以参考下 vue-router 官方文档:Dynamic Route Matching with Params | Vue Router。他的作用是,确保当路由没有匹配到的时候,永远能正确的将页面导航到 404 页面。

当用户在我们 home 页面执行登录操作,登录成功后,保存用户信息,并且动态的添加 vip 路由。

当然,仅仅只是在登录的时候动态新增路由,还不够。

为了用户体验的友好性,我们不可能让用户每次用我们的页面的时候,都先执行一遍登录。

所以,我们就会借用,router 提供的一些列的生命周期函数,来帮助我们确保,当用户信息存在的时候,能自动添加我们的动态路由。

如果你不了解 router 的生命周期的话,可以参考下这个页面:Navigation Guards | Vue Router

beforeEach 方法内,我们每次通过 getUserInfo 方法拿用户的信息。当我们识别到存在用户信息后,就需要判断路由 vip 是否存在,当不存在的时候,动态的创建该路由。

可以说,整个流程下来,是很清晰明了的,逻辑也很通畅。

棘手的问题

如果没有经验的小伙伴,也许看不出来,上面的代码会出现什么问题。

设想下,用户在实际使用我们的系统的时候,可能会通过几种场景进入我们的 vip 页面。

一种场景是,当用户未登录,这时候,也没有 vip 这个路由的存在,那么路由匹配不到,会直接被 noMatch 捕获,然后再进一步重定向到 404 页面。

也就是说,这种未登录的场景下,会被跳转到 404 页面。当然为了用户让用户体验更友好,这时候,其实我们应该先提醒用户登录,登录成功后再激活路由跳转。如果这时候,用户不想登录,那么我们可以直接拒绝用户的跳转需求。

另外一种场景是,当用户已经登陆过了,这时候他点击 vip 路由,想要跳转。这时候,借助我们上面改造过的代码,直接跳转到 vip 页面,不存在任何的问题。

但是还有第三种使用场景是,用户不通过我们的 home 页面进入 vip 页面,他直接在浏览器上输入链接 https://example.com/vip,想一步到位,直接进入我们的 vip 页面。

这第三种使用场景下面,也会分为两种子情景。当用户未登录,自然是拿不到用户信息,也没有 vip 路由,自然会被 noMatch 捕获到,最后重定向到 404 页面;当用户已经登录,系统也存在用户信息,我们通过 getUserInfo 方法也能拿到用户的信息,然后也会进一步成功的动态创建 vip 路由,这时候,你在实际使用的时候,还是会发现,我们仍旧被重定向到了 404 页面了。

这第三种情况下的第二种子情景,就是我们这篇文章想要解决的问题。

我们理想中使用过程中的流程是下面这样的:

image.png

但是却走不通,是为什么呢?

原来真实的流程是下面这样的:

image.png

解决方案

了解了实际的流程走向后,我们就知道该怎么解决这个问题了。

我们可以在动态增加了 vip 路由后,再直接把用户重定向到 vip 页面就行了。

整个流程就会变成下面这样:

image.png

理清了思路以后,我们就可以着手来改造我们的代码了。

// permission.js
router.beforeEach(async (to, from, next) => {
    // 如果存在用户信息
    if (await getUserInfo()) {
        // 如果 vip 这个 router 没有初始化,那么动态的增加 vip 路由
        if (!router.hasRoute("vip")) {
            router.addRoute({ path: '/vip', name: 'vip', component: Vip });
        }

        // 如果当前路由目标是 /404,且来自 /vip
        if (to.name === "NotFound" && to.redirectedFrom?.path === "/vip") {
            // 重定向到 /vip 路由
            next({
                path: to.redirectedFrom.path,
                query: to.redirectedFrom.query,
                replace: true,
            });
            return;
        }
    }
    
    next();
});

我们在添加完 vip 路由以后,可以判断,当前想要加载的路由是否为 404,如果是,并且是由 /vip 跳转而来,则再重定向回去即可。

当然以上的代码只适用于我们文章一开始假设的场景,真实的业务场景中,应该会有更多更复杂的情况,但是万变不离其宗,只要理清了思路,掌握了原理,找到了应对方案,再复杂的场景也能一步步抽丝剥茧,应付起来将会游刃有余。

后记

个人习惯,每次写完一篇技术文章,总想总结点啥,因此都会写个后记。

看起来可能会像是“无病呻吟”,所以,要是不感兴趣的小伙伴可以直接略过即可。

虽然本文中的脉络很清晰,给人感觉好像轻车熟路,能够轻易地药到病除的感觉。但是在实际碰到这个问题的过程中,远没有现在写的这么轻松。

现在总结的时候,可以看作是开了上帝视角,一下子就抓住关键点。但是当你身处漩涡中心的时候,你就是局中人,你是很难跳脱开来,精准定位问题所在的。

不过我挺认同一个观点的,从解决 bug 的能力就能看出一个程序员的水平。

不存在没有 bug 的程序,甚至于有些问题其实就是无解的。大多数情况先,我们能做的也只不过是在权衡利弊的情况下,找到一种最佳的解决方案而已。

共勉之!愿天下没有难解决的 bug!

——————————————

首发自今日头条:https://www.toutiao.com/article/7252565650931991101/

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352

推荐阅读更多精彩内容