本文涉及技術點:html
多級 tabs
切換,tab
項不固定,靈活控制 tab
項內容的展現,以下圖。 vue
目錄結構大概像這樣:api
src緩存
components - 公共組件異步
Tabs
組件pages - 容器組件async
views - 視圖組件,不固定須要動態引入,能夠無限擴展ide
project-exercise工具
從頁面元素的可複用性角度考慮,咱們將將組件按類型分爲公衆組件、容器組件和視圖組件。單元測試
根據對頁面元素的分析,咱們能夠提取選項卡元素爲公共組件,由於兩個地方用到了選項卡切換,因此根據需求進行封裝,代碼以下。測試
<!--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
是由於實際分發的組件內容是由動態組件控制的,起到緩存優化的做用。
容器組件分爲: 一級選項卡容器和二級選項卡容器,一級選項卡內容展現區域又負責渲染二級選項卡及選項卡對應的內容區域。
<--! 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-exercise
、full-cycle
兩個選項中的內容,其餘選項咱們展現一個 EmptyView
組件,效果以下。
二級容器組件是一級容器組件和視圖組件的 中間橋樑
,也就是說一級容器選項卡進行切換時都會渲染二級容器組件,二級容器組件主要負責渲染版本列表和版本對應的視圖組件。
版本號做爲二級選項卡存在,每個一級選項卡的內容展現都會顯示相同的版本列表。
<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>
VersionList
中 template
相似一級容器組件,也是引入公共組件 Tags
,並經過 props
向其傳遞 tabs
,告訴公共組件選顯示什麼樣的項卡數據。
接下來,二級選項卡對應的視圖組件,也是由動態組件 component
控制(分發傳給 Tags
組件中 slot
插槽的內容)。
<component :is="renderViewName" v-if="renderViewName" :planId="getPlanId(current.item)"></component>
與一級容器組件不一樣的是,傳入給 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
階段,處理 fulfilled
和 rejected
兩種狀態,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'; }
點個贊或關注下,會不按期分享技術文章。