枫云教育
您的当前位置:首页Vue的HOC技术如何开发一个无限加载列表(代码示例)

Vue的HOC技术如何开发一个无限加载列表(代码示例)

来源:枫云教育


本文涉及的知识

  • vue

  • vue的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烦死了

    显示全文