一個鮮爲人知的高性能組件註冊及實現組件排序技巧

背景

在使用Vue的路途中,你一定知道如何去註冊並調用一個組件

通常我們會通過三個步驟來實現調用組件的一整個流程

  • 通過import引入組件
  • 在父組件的組件對象components中將導入的子組件註冊
  • 在父組件中使用該組件
<template>
  <div>
    <Child msg="Hello World!"/>
  </div>
</template>

<script>
import Child from "../components/Child";

export default {
  name: "page",
  components: {
    Child
  }
};
</script>

這可能是80%Vue友最常見的一個使用子組件的模式,沒錯,我也經常這麼幹…如果你用膩了這種方式,想換個新奇的,接着往下走,我們再學習兩個不常用的但又很高效的使用方式,提升開發逼格。

場景預設1

想象一下一個會員中心組件,有一部分區域用來展示會員與會員差異的相關展示模塊,但是這兩個只會展示其中一個,會員與非會員。假設我們現在有MemberInfo以及UserInfo兩個組件,我們想根據用戶身份進行對應模塊顯示

實現方案1

這可能是大部分前端玩家會想到的實現方式,包括我自己也常常會如此(捂臉)

<template>
  <div class="container">
    <button @click="isMember = !isMember">{{isMember?'我不想要會員了,哼':'我要成爲會員'}}</button>
    <UserInfo v-if="!isMember"/>
    <MemberInfo v-else/>
  </div>
</template>

<script>
import UserInfo from "../components/UserInfo";
import MemberInfo from "../components/MemberInfo";

export default {
  name: "user",
  data() {
    return {
      isMember: false
    };
  },
  components: {
    UserInfo,
    MemberInfo
  }
};
</script>

實現方案2

通過閱讀官方文檔,我們會發現Vue有提供一個內置組件 component ,渲染一個“元組件”爲動態組件。根據 is 的值,來決定哪個組件被渲染。如果你對此還不太瞭解,文末會奉上官網傳送門.

<template>
  <div class="container">
    <button @click="isMember = !isMember">{{isMember?'我不想要會員了,哼':'我要成爲會員'}}</button>
    <component :is="userComponentName" title="component就是好用喲"/>
  </div>
</template>

<script>
import UserInfo from "../components/UserInfo";
import MemberInfo from "../components/MemberInfo";

export default {
  name: "userComponent",
  data() {
    return {
      isMember: false
    };
  },
  components: {
    UserInfo,
    MemberInfo
  },
  computed: {
    userComponentName() {
      let { isMember } = this;
      return isMember ? "member-info" : "user-info";
    }
  }
};
</script>

從方案1過渡到方案2 ,很明顯我們通過計算屬性配合內置組件component完美剔除了v-if/v-else的邏輯判斷,也可以正常做數據傳遞

只是從現在看來,除了使用方法改變了,沒有啥差異了。這並不是本次想介紹的。現有的兩個模塊組件,我們仍然必須先導入並註冊才能完成調用。現在,我們就不想提前註冊好所需使用的子組件,因爲太麻煩了並且浪費性能,我們想嘗試動態導入註冊使用,讓我們繼續往下,沖沖衝!

實現方案3

<template>
  <div class="container">
    <button @click="isMember = !isMember">{{isMember?'我不想要會員了,哼':'我要成爲會員'}}</button>
    <component :is="userComponentImstance" title="component就是好用喲"/>
  </div>
</template>

<script>
export default {
  name: "userImport",
  data() {
    return {
      isMember: false
    };
  },
  computed: {
    userComponentImstance() {
      let { isMember } = this;
      let pathName = isMember ? "MemberInfo" : "UserInfo";
      //通過import動態導入組件 配合webpack實現組件分離
      return () => import(`../components/${pathName}`);
    }
  }
};
</script>

可以看到,我們不再是提前導入註冊組件的形式來調用了,component直接給其提供一個完整的組件對象
通過閱讀官方文檔我們可以看到 is接收兩種選項之一:

  • 註冊的組件名稱
  • 組件對象

現在,我們通過動態import的形式導入了子組件,配合computed按條件渲染對應的子組件,誰用誰知道。只有在程序運行的時候纔會進行,就省去了我們預先導入和註冊組件的形式,這樣的效果是不是跟使用第三方組件的時候按需加載有個似曾相識的味道…嗯是的,包括我們爲了對SPA性能優化的時候router懶加載有着異曲同工之妙。

當我們改變isMember這個變量就可以實現動態切換組件了。這樣做的好處在於,當我們使用動態導入的時候,webpack會將與導入函數匹配的每個文件單獨創建一個chunk,也就是我們常說的分包加載,而不會一次性加載全部組件。當前樣例並不能看出具體有多大的性能提升,但實際開發中,這個優勢會非常明顯。

如果你熟悉webpack你可以很好的利用這點對代碼進行拆分和DIY屬於自己程序的一個加載策略。具體可以通過瞭解Code Splitting

場景預設2

之前遇到過一個需求,就是給商城首頁的幾個活動模塊根據後端返回順序依次渲染,當時沒想明白怎麼實現。而現在,當你看到這裏的時候,我想你大概有一點點小思路了吧。(如果沒有,那一定是我的錯,文章寫的不夠清晰)。現在知道了內置組件component以及我們的動態加載,我們完全可以很輕鬆的實現。

假設我們父組件爲Home主頁面,可能存在Seckill秒殺、Group團購、Limit限購、Bargain砍價四個活動模塊,模塊展示及排序均由數據決定,而不再是我們hard coded

<template>
  <div>
    <p>假裝這是一個商城首頁</p>
    <button @click="shuffle">隨機切換模塊順序</button>
    <component :is="item.imstance" v-for="(item ,i) in componentImstances" :info="item" :key="i"/>
  </div>
</template>

<script>
export default {
  name: "home",
  data() {
    return {
      moduleList: [
        {
          type: "Bargain",
          title: "砍價",
          startTime: "2019-12-01",
          endTime: "2019-01-01"
        },
        {
          type: "Seckill",

          title: "秒殺",
          startTime: "2019-12-05",
          endTime: "2019-01-01"
        },
        {
          type: "Limit",

          title: "限購",
          startTime: "2019-12-07",
          endTime: "2019-01-01"
        },
        {
          type: "Group",
          title: "團購",
          startTime: "2019-12-11",
          endTime: "2019-01-01"
        }
      ]
    };
  },
  components: {},
  methods: {
    shuffle() {
      let { moduleList } = this;
      let resultArr = [];
      while (moduleList.length > 0) {
        var index = Math.floor(Math.random() * moduleList.length);
        resultArr.push(moduleList[index]);
        moduleList.splice(index, 1);
      }
      this.moduleList = resultArr;
    }
  },
  computed: {
    componentImstances() {
      let { moduleList } = this;
      return moduleList.map(item => {
        item.imstance = () => import(`../components/${item.type}`);
        return item;
      });
    }
  }
};
</script>

這樣就實現了我們的活動模塊的自定義排序了,但是目前我們的模塊動態導入是根據後端返回數據來加載的,沒有人會知道中間會不會出現什麼幺蛾子,爲了避免動態導入的組件在未知情況下加載失敗,我們可以去做一個異常模板提示。
將上面中的componentImstances稍作修改:

computed: {
    componentImstances() {
      let { moduleList } = this;
      return moduleList.map(item => {
        item.imstance = () => {
          return new Promise((resove, reject) => {
            let imstance = import(`../components/${item.type}`);
            imstance.then(res => {
              resove(res);
            });
            imstance.catch(e => {
              resove(import("../components/Error"));
            });
          });
        };
        return item;
      });
    }
  }


我們將動態導入異常的組件捕獲並輸出默認模板,用戶體驗嘛肯定是你的夙願

參考:

Vue內置組件

webpack

總結

靈活運用component以及import,如果一個組件有許多不同的試圖,那這是很有用的,易於擴展。因爲它是異步的,僅在需要的時候才加載模板,使代碼更加輕量。題外話,component配合keep-alive來緩存組件,效果也是頂呱呱噢,本文不再贅述。相關演示源碼已更新到Github傳送門

❤️ 最後

如果你覺得這篇內容對你挺有啓發:

分享出去讓更多的人也能看到這篇內容(歡迎點贊關注😊)

關注公衆號「前端公蝦米」,每週分享前端乾貨

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章