你已是成熟的前端,應該學會本身使用 Vue 高階組件了

高階組件(HOC)是一種架構模式,在React中很是常見,但也能夠在Vue中使用。它能夠被描述爲一種在組件之間共享公共功能而不須要重複代碼的方法。HOC的目的是加強組件的功能。它容許在項目中實現可重用性和可維護性。vue

只要你向一個方法傳入組件,而後返回一個新的組件,這就是一個HOC。git

高階組件在如下方面很是有用:github

  • 操做屬性。
  • 操做數據和數據抽象。
  • 代碼重用

預備知識

在咱們開始教程以前,須要瞭解如下幾點:web

  • 使用Vue框架的經驗。
  • 知道如何使用vue-cli設置應用程序。
  • JavaScript 和 Vue 的基本知識
  • Node (8)
  • npm (5.2.0)

在開始本教程以前,請確保安裝了Node和npm。vue-cli

Vue中的高階組件模式

雖然高階組件一般與React相關聯,可是爲Vue組件建立高階組件是頗有可能的。在Vue中建立高階組件的模式以下所示。npm

// hocomponent.js
    import Vue from 'vue'
    import ComponentExample from '@/components/ComponentExample.vue'

    const HoComponent = (component) => {
      return Vue.component('withSubscription', {
        render(createElement) {
          return createElement(component)
        } 
      }
    }
    const HoComponentEnhanced = HoComponent(ComponentExample);
複製代碼

如上面的代碼塊所示,HoComponent 函數接受一個組件做爲參數,並建立一個新組件來渲染傳進來的組件。bash

一個簡單的 HOC 示例

在本教程中,咱們將介紹一個使用高階組件的示例。在介紹高階組件以前,咱們將瞭解在沒有高階組件的狀況下,當前的代碼庫是如何工做的,而後瞭解如何進行抽象。 codesandbox.io/embed/llvq0…微信

正如上面的CodeSandbox所示,該應用程序會顯示一個紙業公司及其淨資產的列表,以及《辦公室》(美國)中的人物及其獲獎狀況。架構

咱們得到應用程序所需的全部數據來源只有一個,那就是mockData.js 文件。app

// src/components/mockData.js
    const staff = [
        {
          name: "Michael Scott",
          id: 0,
          awards: 2
        },
        {
          name: "Toby Flenderson",
          id: 1,
          awards: 0
        },
        {
          name: "Dwight K. Schrute",
          id: 2,
          awards: 10
        },
        {
          name: "Jim Halpert",
          id: 3,
          awards: 1
        },
        {
          name: "Andy Bernard",
          id: 4,
          awards: 0
        },
        {
          name: "Phyllis Vance",
          id: 5,
          awards: 0
        },
        {
          name: "Stanley Hudson",
          id: 6,
          awards: 0
        }
    ];
    const paperCompanies = [
      {
        id: 0,
        name: "Staples",
        net: 10000000
      },
      {
        id: 1,
        name: "Dundler Mufflin",
        net: 5000000
      },
      {
        id: 2,
        name: "Michael Scott Paper Company",
        net: 300000
      },
      {
        id: 3,
        name: "Prince Family Paper",
        net: 30000
      }
    ];

    export default {
      getStaff() {
        return staff;
      },
      getCompanies() {
        return paperCompanies;
      },
      increaseAward(id) {
        staff[id].awards++;
      },
      decreaseAward(id) {
        staff[id].awards--;
      },
      setNetWorth(id) {
        paperCompanies[id].net = Math.random() * (5000000 - 50000) + 50000;
      }
    };
複製代碼

在上面的代碼片斷中,有幾個const變量保存了公司和員工列表的信息。咱們也導出了一些函數,實現如下功能:

  • 返回員工列表
  • 返回公司列表
  • 增長和減小對特定員工的獎勵,最後
  • 最後,設定公司的淨值

接下來,咱們看看 Staff.vue 和 Companies.vue 組件。

// src/components/Staff.vue
    <template>
      <main>
        <h3>Staff List</h3>
        <div v-for="(staff, i) in staffList" :key="i">
          {{ staff.name }}: {{ staff.awards }} Salesman of the year Award 🎉
          <button @click="increaseAwards(staff.id);">+</button>
          <button @click="decreaseAwards(staff.id);">-</button>
        </div>
      </main>
    </template>
    <script>
      import mockData from "./mockData.js";
      export default {
        data() {
          return {
            staffList: mockData.getStaff()
          };
        },
        methods: {
          increaseAwards(id) {
            mockData.increaseAward(id);
            this.staffList = mockData.getStaff();
          },
          decreaseAwards(id) {
            mockData.decreaseAward(id);
            this.staffList = mockData.getStaff();
          }
        }
      };
    </script>
複製代碼

在上面的代碼塊中,數據實例變量staffList被賦值爲函數mockData.getStaff()返回的內容。咱們也有increaseAwardsdecreaseAwards函數,分別調用mockData.increaseAward 和 mockData.decreaseAward。傳遞給這些函數的id 是從渲染的模板中得到的。

// src/components/Companies.vue
    <template>
      <main>
        <h3>Paper Companies</h3>
        <div v-for="(companies, i) in companies" :key="i">
          {{ companies.name }} - ${{ companies.net
          }}<button @click="setWorth(companies.id);">Set Company Value</button>
        </div>
      </main>
    </template>

    <script>
      import mockData from "./mockData.js";
        export default {
        data() {
          return {
            companies: mockData.getCompanies()
          };
        },
        methods: {
          setWorth(id) {
            mockData.setNetWorth(id);
            this.companies = mockData.getCompanies();
          }
        }
      };
    </script>
複製代碼

在上面的代碼塊中,數據實例變量companies 被賦值爲函數mockData.getCompanies()的返回內容。咱們還有setWorth函數,它經過將公司的 id 傳遞給mockData.setNetWorth來設置一個隨機值做爲淨值。傳遞給函數的id是從渲染的模板中得到的。

如今咱們已經看到了這兩個組件是如何工做的,咱們能夠找出它們之間的共同點,並將其抽象以下:

  • 從數據源獲取數據 (mockData.js)
  • onClick 函數

咱們來看看如何將上面的操做放到高階組件中,以免代碼重複並確保可重用性。

你可使用Vue-cli 繼續建立一個新的Vue項目。Vue CLI是一個用於快速開發Vue.js項目的完整系統,它確保你有一個可用的開發環境,而不須要構建配置。你可使用下面的命令安裝vue-cli

npm install -g @vue/cli

複製代碼

安裝完成後,你可使用下面的命令建立一個新項目,其中vue-hocomponent 是應用程序的名稱。請確保選擇默認預設,由於不須要自定義選項。

vue create vue-hocomponent

複製代碼

安裝完成後,你能夠繼續建立如下文件,而後使用上面共享的片斷內容進行編輯。

  • src/components 目錄下的Staff.vue 文件。
  • src/components 目錄下的 Companies.vue 文件。
  • src/components 目錄下的mockData.js 文件。

或者,你也能夠直接 fork  CodeSandbox  裏的應用跟着本教程操做。

下一步是建立一個用於抽象的高階組件文件。在src文件夾中建立一個名爲HoComponent.js的文件,編輯如下內容:

// src/components/HoComponent.js
    import Vue from "vue";
    import mockData from "./mockData.js";

    const HoComponent = (component, fetchData) => {
      return Vue.component("HoComponent", {
        render(createElement, context) {
          return createElement(component, {
            props: {
              returnedData: this.returnedData
            }
          });
        },
        data() {
          return {
            returnedData: fetchData(mockData)
          };
        }
      });
    };

    export default HoComponent;
複製代碼

在上面的代碼中,Vue 和 mockData 文件中的數據被導入組件。

HoComponent 函數接受兩個參數,一個組件和fetchDatafetchData方法用於肯定要在表示組件中顯示什麼。這意味着不管在哪裏使用高階組件,做爲fetchData 傳遞的函數都將被用來從mockData 中獲取數據。

而後將數據實例returnedData 設置爲fetchData的內容,而後做爲props 傳遞給在高階組件中建立的新組件。

讓咱們看看新建立的高階組件如何在應用程序中使用。咱們須要編輯Staff.vueCompanies.vue

// src/components/Staff.vue
    <template>
      <main>
        <h3>Staff List</h3>
        <div v-for="(staff, i) in returnedData" :key="i">
          {{ staff.name }}: {{ staff.awards }} Salesman of the year Award 🎉
          <button @click="increaseAwards(staff.id);">+</button>
          <button @click="decreaseAwards(staff.id);">-</button>
        </div>
      </main>
    </template>
    <script>
      export default {
        props: ["returnedData"]
      };
    </script>
複製代碼
// src/components/Companies.vue
    <template>
      <main>
        <h3>Paper Companies</h3>
        <div v-for="(companies, i) in returnedData" :key="i">
          {{ companies.name }} - ${{ companies.net
          }}<button @click="setWorth(companies.id);">Set Company Value</button>
        </div>
      </main>
    </template>
    <script>
      export default {
        props: ["returnedData"]
      };
    </script>
複製代碼

正如你在上面的代碼塊中看到的,對於這兩個組件,咱們去掉了函數和數據實例變量,顯示內容所需的全部數據如今都將從這些props中得到。對於刪掉的函數,咱們將很快會講到。

App.vue 組件中,用如下代碼編輯script 標籤中的現有內容:

// src/App.vue
    <script>
      // import the Companies component
      import Companies from "./components/Companies";
      // import the Staff component
      import Staff from "./components/Staff";
      // import the higher order component
      import HoComponent from "./components/HoComponent.js";

      // Create a const variable which contains the Companies component wrapped in the higher order component
      const CompaniesComponent = HoComponent(Companies, mockData => mockData.getCompanies()
      );

      // Create a const variable which contains the Staff component wrapped in the higher order component
      const StaffComponent = HoComponent(Staff, mockData => mockData.getStaff());

      export default {
        name: "App",
        components: {
          CompaniesComponent,
          StaffComponent
        }
      };
    </script>
複製代碼

在上面的代碼塊中,HoComponent用於包裝 Staff 和 Companies 組件。每一個組件做爲HoComponent 的第一個參數傳入,第二個參數是一個函數,它返回另外一個函數,指定應該從mockData獲取什麼數據。這是咱們以前建立的高階組件(HoComponent.js)中的fetchData 函數。

若是你如今刷新應用程序,你應該仍然能夠看到來自mockData 文件的數據像往常同樣呈現。惟一的區別是,這些按鈕沒法工做,由於它們尚未綁定到任何函數。讓咱們解決這個問題。

咱們先修改Staff.vue 和 Companies.vue這兩個文件:

// src/components/Staff.vue
    <template>
      <main>
        <h3>Staff List</h3>
        <div v-for="(staff, i) in returnedData" :key="i">
          {{ staff.name }}: {{ staff.awards }} Salesman of the year Award 🎉
          <button @click="$emit('click', { name: 'increaseAward', id: staff.id });">
          +
          </button>
          <button @click="$emit('click', { name: 'decreaseAward', id: staff.id });">
          -
          </button>
        </div>
      </main>
    </template>
    <script>
      export default {
        props: ["returnedData"]
      };
    </script>
複製代碼
// src/components/Companies.vue
    <template>
      <main>
        <h3>Paper Companies</h3>
        <div v-for="(companies, i) in returnedData" :key="i">
          {{ companies.name }} - ${{ companies.net
          }}<button
          @click="$emit('click', { name: 'setNetWorth', id: companies.id });"
          >
          Set Company Value
          </button>
        </div>
      </main>
    </template>
    <script>
      export default {
        props: ["returnedData"]
      };
    </script>
複製代碼

在上面的兩個代碼片斷中,咱們發送了事件,這些事件將在父組件App.vue中使用。咱們發送了一個對象,它包含值、與試圖執行的操做相關聯的函數名以及被單擊元素的id。別忘了mockData.js 文件中定義了increaseAwarddecreaseAward 和setNetWorth 函數。

接下來,咱們開始編輯父組件App.vue ,讓其對子組件發送過來的數據進行響應。App.vue 更改以下:

// src/App.vue
    <template>
      <div id="app">
        <CompaniesComponent @click="onEventHappen" />
        <StaffComponent @click="onEventHappen" />
      </div>
    </template>

    <script>
      // import the Companies component
      import Companies from "./components/Companies";
      // import the Staff component
      import Staff from "./components/Staff";
      // import the higher order component
      import HoComponent from "./components/HoComponent.js";
      // import the source data from mockData only to be used for event handlers
      import sourceData from "./components/mockData.js";
      // Create a const variable which contains the Companies component wrapped in the higher order component
      const CompaniesComponent = HoComponent(Companies, mockData =>
      mockData.getCompanies()
      );
      // Create a const variable which contains the Staff component wrapped in the higher order component
      const StaffComponent = HoComponent(Staff, mockData => mockData.getStaff());

      export default {
        name: "App",
        components: {
          CompaniesComponent,
          StaffComponent
          },
        methods: {
          onEventHappen(value) {
            // set the variable setFunction to the name of the function that was passed iin the value emitted from child component i.e. if value.name is 'increaseAward', setFunction is set to increaseAward()
            let setFunction = sourceData[value.name];
            // call the corresponding function with the id passed as an argument.
            setFunction(value.id);
          }
        }
      };
    </script>

    <style>
    #app {
      font-family: "Avenir", Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
      margin-top: 60px;
    }
    </style>
複製代碼

在上面的代碼塊中,咱們在App.vue組件中添加了一個事件監聽器。vue的組件。每當Staff.vue 或Companies.vue 組件被點擊時,onEventHappen 方法將會被調用。

onEventHappen方法中,咱們將變量setFunction 設置爲從子組件發出的值中傳遞的函數名,也就是說,若是value.name是'increaseAward',那麼setFunction設置爲increaseAward()setFunction 將以id做爲參數執行。

最後,爲了將事件監聽器傳遞給封裝在高階組件中的組件,咱們須要在 HoComponent.js文件中添加下面這行代碼。

props: {
    returnedData: this.returnedData
    },
    on: { ...this.$listeners }
複製代碼

你如今能夠刷新應用程序,全部的按鈕均可以正常工做。

vue-hoc

或者,您可使用vue-hoc庫來幫助建立高階組件。vue-hoc幫助你輕鬆地建立高階組件,你要作的就是傳遞基本組件、應用於HOC的一系列組件選項和在渲染階段傳遞給組件的數據屬性。

vue-hoc 可用以下命令安裝:

npm install --save vue-hoc

複製代碼

vue-hoc插件有一些例子可讓你開始建立更高階的組件,你能夠查看這裏.

總結

在本教程中咱們瞭解到,高階組件的主要用途是加強應用程序中表現類組件的可重用性和邏輯。 另外還了解到,高階組件在如下方面有用處:

  • 操做屬性。
  • 操做數據和數據抽象。
  • 代碼重用

而後咱們看了一個如何在 Vue 中建立和使用高階組件的例子。例子的源碼可在GitHub上查看。

交流

歡迎關注微信公衆號「1024譯站」

公衆號:1024譯站
相關文章
相關標籤/搜索