記一次 Vue 組件設計及使用 computed 動態引入組件

本文涉及技術點:html

  • 動態組件 & 異步組件
  • 內置組件 keep-alive & transition
  • 插槽 slot 及 v-slot

實際場景

多級 tabs 切換,tab 項不固定,靈活控制 tab 項內容的展現,以下圖。 vue

image.png

目錄結構

目錄結構大概像這樣:api

  • src緩存

    • components - 公共組件異步

      • Tabs.vue - 封裝的 Tabs 組件
      • EmptyView.vue - 空頁面組件
      • *.vue - 其餘公共組件
    • pages - 容器組件async

      • Index.vue - 主要處理一級 tabs 數據及對應的內容渲染
      • VersionList.vue 主要處理二級 tabs 數據及對應的內容渲染
    • views - 視圖組件,不固定須要動態引入,能夠無限擴展ide

      • project-exercise工具

        • Index.vue
      • ...

組件設計

從頁面元素的可複用性角度考慮,咱們將將組件按類型分爲公衆組件、容器組件和視圖組件。單元測試

公共組件

根據對頁面元素的分析,咱們能夠提取選項卡元素爲公共組件,由於兩個地方用到了選項卡切換,因此根據需求進行封裝,代碼以下。測試

<!--src/components/Tags.vue -->
<template>
  <el-tabs v-model="active" :type="type" @tab-click="handleClick">
    <el-tab-pane v-for="item in tabs" :key="item.id" :name="item.name" :label="item.label"></el-tab-pane>
    <transition name="component-fade" mode="out-in">
      <keep-alive>
        <slot :item="currentTab"></slot>
      </keep-alive>
    </transition>
  </el-tabs>
</template>
咱們封裝的組件 Tags 中,使用 elementUI中的 tabs 組件(類庫能夠隨意選擇,不要受工具限制)。

公共組件 Tags 由兩部分構成:

  • tabs 切換欄 - 切換欄數據由外部控制,經過 props 注入。
  • 內容展現區域 - 內容展現區域由 slot 進行控制。

之因此 slot 外層包裹 keep-alive 是由於實際分發的組件內容是由動態組件控制的,起到緩存優化的做用。

一級容器組件 Index

容器組件分爲: 一級選項卡容器和二級選項卡容器,一級選項卡內容展現區域又負責渲染二級選項卡及選項卡對應的內容區域。
<--! src/pages/Index.vue-->
<template>
  <div>
    <v-tags :activeName="activeName" :type="tabType" :tabs="tabs" v-slot="current">
      <component :is="getCurrentTab(current.item)" :tab="getCurrentTab(current.item)"></component>
    </v-tags>
  </div>
</template>
<script>
  import VTags from '@/components/Tabs';
  import EmptyView from '@/components/EmptyView';
  import VersionList from './VersionList';
  export default {
    components: {
      VTags,
      EmptyView,
      ProjectExercise: VersionList,
      FullCycle: VersionList
    },
    data() {
      return {
        tabType: 'card',
        activeName: 'project-exercise',
        tabs: [...]     
      }
    },
    methods: {
      // 根據 tabName 渲染不一樣組件
      getCurrentTab(name) {
        const tabName = [
          'project-exercise', 
          'full-cycle'
        ]
        return tabName.includes(name) ? name : 'empty-view';
      }
    }, 
  }
</script>

一級容器組件作的事情:

  • 負責告訴公共組件 Tabs 渲染哪些 tabs 數據。
  • 負責控制一級選項卡進行切換時,渲染對應的內容組件。

所以經過 props 傳入給 Tabs 用來渲染的 tabs 數據可能像這樣:

tabs: [
  { id: '0',
    label: '項目策劃',
    name: 'project-exercise'
  },
  { id: '1',
    label: '需求分析',
    name: 'demand-analysis'
  },
  { id: '2',
    label: '設計編碼',
    name: 'design-encoding'
  },
  { id: '3',
    label: '單元測試',
    name: 'unit-test'
  },
  { id: '4',
    label: '組裝測試',
    name: 'assembly-test'
  },
  { id: '5',
    label: '確認測試',
    name: 'confirmation-test'
  },
  { id: '6',
    label: '全生命週期',
    name: 'full-cycle'
  }
]

一級選項卡渲染出來的結果像下圖所示。

分發給 Tabs 組件的 slot 插槽的內容經過動態組件 component 控制。

<component :is="getCurrentTab(current.item)" :tab="getCurrentTab(current.item)"></component>

is 屬性的值由公共組件 Tabs 傳入,傳入的值與 name 值對應,由 v-slot 接受。最後對處理傳入的值進行匹配操做,以下代碼。

methods: {
  // 根據 tabName 渲染不一樣組件
  getCurrentTab(name) {
    const tabName = [
      'project-exercise', 
      'full-cycle'
    ]
    return tabName.includes(name) ? name : 'empty-view';
  }
},

根據需求咱們只渲染 project-exercisefull-cycle 兩個選項中的內容,其餘選項咱們展現一個 EmptyView 組件,效果以下。

二級容器組件 VersionList

二級容器組件是一級容器組件和視圖組件的 中間橋樑,也就是說一級容器選項卡進行切換時都會渲染二級容器組件,二級容器組件主要負責渲染版本列表和版本對應的視圖組件。

版本號做爲二級選項卡存在,每個一級選項卡的內容展現都會顯示相同的版本列表。
<template>
  <div>
    <v-tags :type="tabType" :tabs="tabs" v-slot="current">
      <component :is="renderView" v-if="renderView" :planId="getPlanId(current.item)"></component>
      <!-- <own-view :planId="getPlanId(current.item)"></own-view> -->
    </v-tags>
  </div>
</template>
<script>
  //import OwnView from "../views/project-exercise/";
</script>

VersionListtemplate 相似一級容器組件,也是引入公共組件 Tags,並經過 props 向其傳遞 tabs ,告訴公共組件選顯示什麼樣的項卡數據。

接下來,二級選項卡對應的視圖組件,也是由動態組件 component 控制(分發傳給 Tags 組件中 slot 插槽的內容)。

<component :is="renderViewName" v-if="renderViewName" :planId="getPlanId(current.item)"></component>

computed 動態引入異步組件

與一級容器組件不一樣的是,傳入給 is 屬性的值不是組件名,而是組件實例,這裏渲染的視圖組件不是經過固定路徑引入,而是經過 import 動態引入的,這裏也是本文的重點 computed 動態引入組件, 具體實現代碼以下。

<template>
  ...
</template>
<script>
import VTags from "@/components/Tabs";
import { getProjectPlans } from "@/api";
export default {
  props: ["tab"],
  components: {
    VTags
  },
  data() {
    return {
      tabType: "border-card",
      tabs: [],
      renderView: null, 
      view: this.tab //tab 名
    };
  },
  watch: {
    tab() {
      this.view = this.tab;
      this.init()
    }
  },
  computed: {
    // 經過計算屬性動態引入組件
    loaderWiew() {
      return () => import("../views/" + this.view + "/Index.vue");
    }
  },

  methods: {
    // 根據 name 得到 planId
    getPlanId(name) {
      let filterTabs = this.tabs.filter(item => item.name == name);
      if (filterTabs.length) {
        return filterTabs[0].id;
      }
    },
    init() {
      this.loaderWiew().then(() => {
        // 動態加載組件
        // this.loaderWiew() 爲組件實例
        this.renderView = () => this.loaderWiew();
      }).catch(() => {
        // 組件不存在時處理
        this.renderView = () => import("@/components/EmptyView.vue");
      });
    }
  },
  mounted() {
    this.init();  
    // 省略經過接口獲取版本列表數據的邏輯
  }
};
</script>

爲何使用 computed 去動態引入組件,而不是像這樣:

<template>
  ...
</template>
<script>
import VTags from "@/components/Tabs";
const OwnView = import("../views/" + this.view + "/Index.vue");
export default {
  components: {
    VTags,
    OwnView
  },
}
</script>

要知道,在 export defaul {} 外部是沒法獲取 vue 實例的,所以就沒法與外部進行通訊,獲取不到外部傳入的 this.view 變量。

所以咱們只能經過引入異步組件的概念,來動態引入組件。

首先咱們在計算屬性中建立異步組件的實例,返回 Promise

computed: {
  // 返回 Promise
  loaderWiew() {
    return () => import("../views/" + this.view + "/Index.vue");
  }
},

在組件掛載 mouted 階段,處理 fulfilledrejected 兩種狀態,fulfilled 正常渲染,rejected 則渲染 EmptyView 組件。

init() {
  this.loaderWiew().then(() => {
    // 動態加載組件
    // this.loaderWiew() 爲組件實例
    this.renderViewName = () => this.loaderWiew();
  }).catch(() => {
    // 組件不存在時處理
    this.renderViewName = () => import("@/components/EmptyView.vue");
  });
}
...
mounted() {
  this.init();
}

this.view 的值與 views 目錄下的子目錄匹配,匹配成功,表明成功引入。(後續開發中,視圖組件能夠無限擴展)

接着,經過 watch 監聽數據變化,從新初始化組件。

watch: {
  tab() { // tab 經過 props 傳入,傳入的值與目錄名稱對應
    this.view = this.tab; // 
    this.init(); // 由變化時,說明二級 tab 進行了切換,從新渲染
  }
},

最後,視圖組件引入成功,正常渲染。

引入失敗,渲染 EmptyView 組件。

最後

一個完整的多級 tabs 切換組件就設計完成了,支持無限 view 層組件擴展,能夠根據需求靈活控制 tab 項內容的展現。

getCurrentTab(name) {
    const tabName = [
      'project-exercise', 
      'full-cycle'
    ]
    return tabName.includes(name) ? name : 'empty-view';
}

點個贊或關注下,會不按期分享技術文章。

相關文章
相關標籤/搜索