在web開發上,咱們都對數據採用分頁加載的機制,一種變形就是在頁面採用循環加載的機制,拉到頁面最下方有個加載更多的按鈕。問題在於,當不一樣的數據要展現時,就要寫不少這種列表,可是其中的邏輯都是類似的。css
那有沒有這麼一個組件,來完成這一切相同的邏輯呢?html
須要有這麼一個InfiniteList組件,它負責管理相關數據的加載和維護,而後以列表的形式顯示出來,而列表項必須是由調用方決定的組件。前端
高階組件的概念,是React裏面常常提到的,相似於高階函數。
高階函數:(fn) => otherFn
高階組件:component => otherComponent
高階組件用是代碼複用的優秀工具,主要在處理邏輯方面和普適性上,有着奇效。vue
因此我決定用HOC來實現這個需求git
參考文章: http://hcysun.me/2018/01/05/%...
良心博客
我使用的是vue和iview UI庫程序員
先弄出UI框架先,我用一個vue文件來構建整個組件的基本框架。源代碼地址github
<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
一些UI有關的數據(不是很重要)數組
props: { loadTip: { type: String, default: "加載更多" } ... }, computed: { loadButtonText() {}, tipIcon() {} }
這部分比較重要的只有一個事件發射,將點按鈕的行爲轉換爲 請求加載數據app
handleClickLoad() { // 發射 請求加載數據的 事件 this.$emit("on-load"); }
接下來就是最重要的部分,編寫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; } } },
接下來使用一遍
<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煩死了