Vue Router根據後臺資料載入不同的元件(思考->實現->不止於實現)

zhea55發表於2021-08-08

實際專案中遇到的需求

同一個連結需要載入不同的頁面元件。根據使用者所購買服務的不同,有不同的頁面展現。

有一些不好的實現方式

  1. 直接把這幾個元件寫在同一個元件下,通過v-if去判斷。如果這麼做的話,甚至可以不使用vue-router,直接把所有元件,都寫在一個檔案裡面,全部通過v-if判斷,也是可行的。(前提是幾萬行程式碼一起,你不嫌麻煩的話)
  2. 在渲染這個連結的時候,直接去請求後臺的資料,通過資料渲染不同的連結。(理論上是可行的,但如果使用者沒有用這個功能,這些連結每次都提前取了後臺資料;另外如果使用者知道了連結,直接訪問連結,還是需要邏輯去判斷使用者該看到哪個頁面)
  3. 通過呼叫router.beforeEach,對每個路由進行攔截,當路由為我們指定的路由時,請求後臺資料,動態跳轉頁面。(功能是可以完成,但實際上,這只是整個系統的一小塊功能,不應該侵入整個路由系統,如果每個業務頁面,都寫在全域性路由系統,也會導致路由的邏輯過於複雜)

個人感覺比較好的實現方式

在配置路由的地方獲取伺服器資料動態載入對應的元件

{
  path: 'shopKPI',
  // 如果提前把後臺資料存到store裡面,在這裡訪問store資料,可以直接判斷出來
  // 但這種特定業務頁面的資料放全域性store,其他地方也不用,實在沒有必要
  component: () => import('@/views/store/dataVersion'),
  name: 'store_KPI',
  menuName: '店鋪參謀',
  meta: {
    codes: ['storeProduct.detail']
  }
}

理想很美好,現實的情況是,component接收的這個方法必須要同步的返回一個promise。

這時候我想到了上面不好的實現方式1,稍微加以改造

<!-- ChooseShopKPI.vue -->
<template>
  <dataVersion v-if="!useNewShopKPI" />
  <ShopKPI v-else />
</template>

<script>
import { get } from 'lodash';
import { getStoreReportFormVersion } from '@/api/store';
import dataVersion from './dataVersion';
import ShopKPI from './ShopKPI';

export default {
  name: 'ChooseShopKPI',

  components: {
    dataVersion,
    ShopKPI,
  },

  data() {
    return { useNewShopKPI: false };
  },

  created() {
    getStoreReportFormVersion().then((res) => {
      if (get(res, 'data.data.new')) {
        this.useNewShopKPI = true;
      }
    });
  },
};
</script>

<style lang="css" scoped></style>

把路由渲染對應的頁面,改為渲染這個中間頁面ChooseShopKPI

{
  path: 'shopKPI',
  // 如果提前把後臺資料取到,在這裡訪問store資料,可以直接判斷出來
  // 但這種特定業務頁面的資料放全域性store,其他地方也不用,實在沒有必要
-  component: () => import('@/views/store/dataVersion'),
+  component: () => import('@/views/store/ChooseShopKPI'),
  name: 'store_KPI',
  menuName: '店鋪參謀',
  meta: {
    codes: ['storeProduct.detail']
  }
}

這樣就實現了我們期望的功能。

功能已實現,但我又開始了新的思考

這種方式雖然很好的解決了動態載入頁面元件的問題。但也產生了一些小問題。

  1. 如果這種通過伺服器載入資料的頁面後續增加的話,會出現多個ChooseXXX的中間頁面。
  2. 這種中間頁面,實際上是做了二次路由,不熟悉邏輯的開發人員可能並不清楚這裡面的頁面跳轉邏輯,增加了理解成本。

最終方案——高階元件

通過對ChooseXXX進行抽象,改造為DynamicLoadComponent

<!-- DynamicLoadComponent.vue -->
<template>
  <component :is="comp"  />
</template>

<script>
export default {
  name: 'DynamicLoadComponent',
  props: {
    renderComponent: {
      type: Promise,
    },
  },
  data() {
    return {
      comp: () => this.renderComponent
    }
  },
  mounted() {},
};
</script>

<style lang="css" scoped></style>

直接在路由的配置中獲取後臺資料,並進行路由的分發。這樣路由邏輯都集中在路由配置檔案中,沒有二次路由。維護起來不會頭疼腦脹。

DynamicLoadComponent元件也得以複用,後續新增判斷後臺資料載入頁面的路由配置,都可以導向這個中間元件。

{
  path: 'shopKPI',
  component: () => import('@/views/store/components/DynamicLoadComponent'),
  name: 'store_KPI',
  menuName: '店鋪參謀',
  meta: {
    codes: ['storeProduct:detail'],
  },
  props: (route) => ({
    renderComponent: new Promise((resolve, reject) => {
      getStoreReportFormVersion()
        .then((responseData) => {
          const useNewShopKPI = get(responseData, 'data.data.shop_do');
          const useOldShopKPI = get(
            responseData,
            'data.data.store_data_show'
          );

          if (useNewShopKPI) {
            resolve(import('@/views/store/ShopKPI'));
          } else if (useOldShopKPI) {
            resolve(import('@/views/store/dataVersion'));
          } else {
            resolve(import('@/views/store/ShopKPI/NoKPIService'));
          }
        })
        .catch(reject);
    }),
  })
}

檢視線上小例子(只支援chrome)
https://stackblitz.com/edit/vuejs-starter-jsefwq?file=index.js

相關文章