在nuxt中使用路由重定向
我们都知道,在写SPA的时候,我们可以通过配置vue-router来实现路由的重定向。官方文档(以及ts类型)的定义中给出了这一选项:interface RouteConfig = {path: string,redirect?: string | Location | Function,}也就是说,我们可以定义这样一个路由:{path: "/foo",r...
我们都知道,在写SPA的时候,我们可以通过配置vue-router来实现路由的重定向。官方文档(以及ts类型)的定义中给出了这一选项:
interface RouteConfig = {
path: string,
redirect?: string | Location | Function,
}
也就是说,我们可以定义这样一个路由:
{
path: "/foo",
redirect: "/foo/bar",
}
这样,我们在访问/foo
的时候,就会被重定向到/foo/bar
。这些都是老生常谈了。
然而,到了SSR的环境下,如果使用nuxt,因为nuxt采用了约定大于配置的方式,pages目录代替了路由,虽然简化了配置,但是给一些需要定制化的场景的手动配置带来了一些麻烦,并且nuxt官方也不建议手动配置router,如果实在需要配置,可以在nuxt.config.js里进行一些中间件配置,但是这个对于重定向这种特别简单的事情来说,未免有杀鸡用牛刀之嫌。
所以,我一开始想的办法是,在pages目录下,需要重定向的路由组件里,增加一个beforeCreate()
钩子:
<template>
<div></div>
</template>
<script>
export default {
beforeCreate() {
this.$router.replace('/path/to')
}
}
</script>
相当于在组件创建之前进行一个路由的替换,这个组件作为路由的占位。之所以不用push而是用replace,因为replace更接近重定向的效果,我们总不希望用户还能回退(比如浏览器的后退键)到重定向之前的页面里去吧。
但是这个方案有一个问题,就是在路由“重定向”的过程中,界面会发生轻微的闪烁,这样体验就很不好了。所以,肯定需要其他的解决方案。至于为什么会闪屏,因为虽然beforeCreate
钩子理论上会先于模板编译执行,但是这是在SFC环境下,模板编译会提前执行;如果是用script标签引入的Vue就不会有这个问题:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="https://unpkg.com/vue@2.6.11/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router@3.1.6/dist/vue-router.js"></script>
</head>
<body>
<div id="app">
<router-view/>
</div>
<script>
const Foo = {template: '<div>foo</div>'};
const Bar = {template: '<div>bar</div>'};
const routes = [
{path: '/foo', component: Foo, redirect: '/bar'},
{path: '/bar', component: Bar}
];
const router = new VueRouter({routes});
const app = new Vue({
el: '#app',
router
})
</script>
</body>
</html>
如果有需要,可以进一步参考Vue官方的生命周期图示。
查了一下文档,好在nuxt提供了这么一个重定向的API,就藏在context对象里。事实上,下面所有的解决方案,都是基于这个context对象进行的:
属性字段 | 类型 | 可用 | 描述 |
---|---|---|---|
redirect | Function | 客户端 & 服务端 | 用这个方法重定向用户请求到另一个路由。状态码在服务端被使用,默认 302 redirect([status,] path [, query]) |
同时,我们还知道:
asyncData
方法会在组件(限于页面组件)每次加载之前被调用。它可以在服务端或路由更新之前被调用。在这个方法被调用的时候,第一个参数被设定为当前页面的上下文对象,你可以利用asyncData
方法来获取数据并返回给当前组件。
所以,我们可以这么写:
<template>
<div></div>
</template>
<script>
export default {
asyncData({ redirect }) {
redirect('/path/to')
}
}
</script>
这样就可以解决问题了。可能你会想为什么不用fetch来进行路由跳转,因为fetch是用来处理vuex store的数据的,我个人认为从语义上不适合处理路由跳转的任务;当然,实际上是可以正常运行的,反正都是基于context对象进行的操作。
如果你对上面那个空的div感到不满意,觉得不优雅,你也可以搞得更极端一点,使用渲染函数来渲染模板的根节点:
<script>
export default {
asyncData({ redirect }) {
redirect('/path/to')
},
render(h) {
return h('div')
}
}
</script>
这样看起来可能更简洁一点。
但是这种写法对于路由鉴权那种场景是不太适用的。比如,我需要在进行路由跳转前验证用户的身份,我总不能在每个page里都写这么一段吧,维护起来也不方便,如果路由改了,每个页面都得改。所以,这个时候就得用到nuxt提供的中间件机制了。中间件可以在page层面配置,也可以全局配置,参考官方的例子:
pages/secret.vue
<template>
<h1>Secret page</h1>
</template>
<script>
export default {
middleware: 'authenticated'
}
</script>
middleware/authenticated.js
export default function ({ store, redirect }) {
// If the user is not authenticated
if (!store.state.authenticated) {
return redirect('/login')
}
}
这个也放到nuxt.config.js里,变成全局的配置:
module.exports = {
router: {
middleware: 'authenticated'
}
}
但是有一点需要注意,也是只要使用路由就需要注意的一个问题:避免循环重定向。这和避免写死循环是一个道理。
总结一下:
- 如果是少数几个页面之间的固定的重定向逻辑,可以直接用
asyncData
(或者fetch
,虽然我个人觉得语义不好)参数里context的redirect
来进行重定向; - 如果需要重定向的页面数量较多(可以考虑使用中间件 + 表驱动),或者存在一些动态变化的重定向逻辑(比如路由鉴权),可以考虑使用中间件机制。
更多推荐
所有评论(0)