使用Vue的HOC技術開發一個無限加載列表

前言

在web開發上,咱們都對數據採用分頁加載的機制,一種變形就是在頁面採用循環加載的機制,拉到頁面最下方有個加載更多的按鈕。問題在於,當不一樣的數據要展現時,就要寫不少這種列表,可是其中的邏輯都是類似的。css

  1. 維護一組數據
  2. 加載更多數據
  3. 將數據用對應的組件顯示出來
  4. 處理加載狀態等

那有沒有這麼一個組件,來完成這一切相同的邏輯呢?html

需求

須要有這麼一個InfiniteList組件,它負責管理相關數據的加載和維護,而後以列表的形式顯示出來,而列表項必須是由調用方決定的組件前端

HOC

高階組件的概念,是React裏面常常提到的,相似於高階函數。
高階函數:(fn) => otherFn
高階組件:component => otherComponent
高階組件用是代碼複用的優秀工具,主要在處理邏輯方面和普適性上,有着奇效。vue

因此我決定用HOC來實現這個需求git

參考文章: http://hcysun.me/2018/01/05/%...
良心博客

本文涉及的知識

  • vue
  • vue的render函數

實現

0

我使用的是vue和iview UI庫程序員

1

先弄出UI框架先,我用一個vue文件來構建整個組件的基本框架。源代碼地址github

  • html部分
<template>
  <div class="wrapper">
    <div class="content-wrapper">
      <slot></slot>
    </div>
    <div class="load-wrapper">
      <Button
        :icon="tipIcon"
        type="text"
        v-bind:disabled="!hasMore"
        v-bind:style="{color: tipColor}"
        v-bind:loading="loading"
        v-on:click="handleClickLoad">
        {{loadButtonText}}
      </Button>
    </div>
  </div>
</template>

用一個slot來分發要循環渲染的項目web

  • js部分

一些UI有關的數據(不是很重要)數組

props: {
      loadTip: {
        type: String,
        default: "加載更多"
      }
      ...
    },
    computed: {
      loadButtonText() {},
      tipIcon() {}
    }

這部分比較重要的只有一個事件發射,將點按鈕的行爲轉換爲 請求加載數據app

handleClickLoad() {
        // 發射 請求加載數據的 事件
        this.$emit("on-load");
      }
  • css部分略

2

接下來就是最重要的部分,編寫HOC
首先要明白,Vue中的組件,究竟是什麼。像咱們寫一個Vue文件,export出的是一個對象,因此咱們如今寫HOC,其實也是要最後返回一個對象。
因此我寫了下面的函數來生成HOC

/**
 * 使用高階組件的辦法實現了一個無限加載列表
 * 能夠根據數據循環渲染出特定的組件,而且管理加載狀態
 * @param component 具體項的組件 {props: {data}}
*/
function InfiniteList(listItem) {
    return {
        props:...
        data(){}
        ...
    }
}

而咱們若是渲染呢,固然是用Vue的render函數

render(h) {
    return h(component, data, children);
}

咱們使用組合的方式,最外層須要用到咱們第1步寫到的模板,因而導入它,並註冊它

import InfiniteListTemplate from "./InfiniteListTemplate";
function InfiniteList(listItem) {
    return {
        ...
        components: {
          InfiniteListTemplate  //  列表框架的模板,這個模板裏面只有ui表現
        },
        ...
    }
}

render函數對於熟悉React的程序員來講應該是不難的,官網也有很詳細的介紹。

render(h) {
      const self = this;
      // 根據 data 的 dataList循環渲染子組件
      const listItems = ...

      return h(InfiniteListTemplate, {
        props: {
          ...self.$props, // 傳遞全部參數
          hasMore: self.hasMore,  // 另外的hasMore和loading是這個HOC的state
          loading: self.loading
        },
        attrs: self.$attrs,
        on: {
          // 監聽加載按鈕事件
          "on-load": () => self.handleLoadData()
        }
      }, listItems);
    },

這裏在最外層渲染咱們的模板(且稱爲模板組件),並將當前HOC的props,attrs傳遞給模板組件。
這裏提到了HOC的data,很是簡單,就是兩個狀態和一個數據數組

data() {
      return {
        hasMore: true,
        loading: false,
        dataList: []
      }
    }

而後呢,循環渲染在哪?別急,render中的listItems就是咱們循環渲染出來的組件,這裏使用了map,相信使用React的人很是熟悉這種風格

const listItems = this.dataList.map(item => h(component, {
            props: {
              data: item
            }
          })
        );

最終返回的就是

return h(InfiniteListTemplate, {options}, listItems);

在哪裏維護數據呢?固然是要傳入一個加載數據的函數來進行管理,咱們在HOC的props裏面定義

props: {
      tipColor,
      loadTip,
      loadingTip,
      // 上面的數據都是爲了傳給模板(組件)
      offset: {
        type: Number,
        default: 5
      },
      // 數據加載的函數,須要的是一個 (index, offset) => Promise<[]>
      loadDataFunc: {
        type: Function,
        default() {
          return (index, offset) => Promise.resolve(new Array(offset).map((o, i) => index + i));
        }
      }
    },

而後咱們還記得模板函數發射了個on-load事件麼?咱們須要在HOC裏監聽它而且處理邏輯

render(h) {
    return h(InfiniteListTemplate, {
        ...
        on: {
            'on-load': () => self.handleLoadData()
        }
    }, listItems);
},
methods: {
      /**
       * 監聽模板點出了加載按鈕時的操做
       * 調用數據加載函數加載數據
       * @return {Promise<void>}
       */
      async handleLoadData() {
        try {
          this.loading = true;
          let res = await this.loadDataFunc(this.dataList.length, this.offset);
          if (res && res.length) {
            this.dataList = this.dataList.concat(res);
            this.$Message.success(`成功獲取到${res.length}條新數據`);
          } else {
            this.$Message.info(`已經獲取了所有數據了`);
            this.hasMore = false;
          }
        } catch (e) {
          this.$Message.error("加載失敗" + e.message);
        } finally {
          this.loading = false;
        }
      }
    },

完整InfiniteList.js代碼

3

接下來使用一遍

<script>
import MyComponent from "./components/MyComponent";
import InfiniteList from "./components/hoc/InfiniteList";
const InfiniteListComponent = InfiniteList(MyComponent);
...

data() {
    loadDataFunc: (index, offset) => Promise<[]>
}
</script>

<template>
  <div id="app">
    <InfiniteListComponent
      v-if="loadDataFunc"
      v-bind:load-data-func="loadDataFunc">
    </InfiniteListComponent>
  </div>
</template>

MyComponent.vue是個很是簡單的組件

<template>
  <div>Hello</div>
</template>

<script>
  export default {
    name: "MyComponent",
    props: {
      data: {
        type: String
      }
    }
  }
</script>

效果圖以下
圖片描述

總結

在前端開發過程當中,HOC是代碼利用的利器,可是對抽象的要求高。我以爲本身愛上了React...Vue實現這個HOC煩死了

相關文章
相關標籤/搜索