你已是成熟的前端,应该学会本身使用 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译站
相关文章
相关标签/搜索