在nuxt中使用路由重定向

我們都知道,在寫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/[email protected]/dist/vue.js"></script>
    <script src="https://unpkg.com/[email protected]/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來進行重定向;
  • 如果需要重定向的頁面數量較多(可以考慮使用中間件 + 表驅動),或者存在一些動態變化的重定向邏輯(比如路由鑑權),可以考慮使用中間件機制。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章