前言在web开发上,我们都对数据采用分页加载的机制,一种变形就是在页面采用循环加载的机制,拉到页面最下方有个加载更多的按钮。问题在于,当不同的数据要展示时,就要写很多这种列表,但是其中的逻辑都是相似的。维护一组数据加载更多数据将数据用对应的组件显示出来处理加载状态等那有没有这么一个组件,来完成这一切相同的逻辑呢?需求需要有这么一个InfiniteList组件,它负责管理相关数据的加载和维护,然后以列表的形式显示出来,而列表项必须是由调用方决定的组件。HOC高阶组件的概念,是React里面经常提到的,类似于高阶函数。高阶函数:(fn) => otherFn高阶组件:component => otherComponent高阶组件用是代码复用的优秀工具,主要在处理逻辑方面和普适性上,有着奇效。所以我决定用HOC来实现这个需求参考文章:http://hcysun.me/2018/01/05/%…良心博客本文涉及的知识vuevue的render函数实现0我使用的是vue和iview UI库1先弄出UI框架先,我用一个vue文件来构建整个组件的基本框架。源代码地址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来分发要循环渲染的项目js部分一些UI有关的数据(不是很重要) props: { loadTip: { type: String, default: “加载更多” } … }, computed: { loadButtonText() {}, tipIcon() {} }这部分比较重要的只有一个事件发射,将点按钮的行为转换为 请求加载数据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烦死了