我们在写组件时候,通常会大致先分为两种,业务组件和通用组件,业务组件通和业务逻辑相关,一般作为一个业务模块的局部组件, 比如列表中的列表项组件;通用组件适用面广,不会和业务有牵扯,比如弹出框组件。
所以我们开始封装一个组件的时候,就要划分业务逻辑,做什么,不做什么,从外部接收什么,向外部提供什么,这个边界应该非常清楚
但是之前的封装的loadmore组件不太符合这一点,可能是项目一开始比较关注功能的实现,将其当成的一个业务组件撰写,现在有一点需要优化:
之前我们传入了各种请求相关的参数,包括url在组件内部完成加载和页码控制等一系列操作,显然这不太符合组件功能职责单一化的原则, 其实组件内部并不关心加载到第几页或者是需要请求什么后端接口,而只要父组件告诉自己是否还可以加载就可以了, 至于加载请求列表,子组件通知父组件去加载就OK。
最终我们得到一个和业务完全分离的通用组件,代码如下:
<template> <div ref="loadmore"> <div> <slot></slot> </div> <div> <span v-if="loading"> <i></i> <span>正在加载</span> </span> <span v-else-if="loading">正在加载...</span> <span v-else>没有更多了</span> </div> </div> </template> <script> export default { props: { /** * 是否加载 */ loading: { type: Boolean, default: false, }, /** * 滚动外部容器, 选择器字符串 */ container: { default: () => (global), }, /** * 距离底部多远加载 */ distance: { type: Number, default: 200, }, }, data() { return { dom: null, // 外部容器dom } }, mounted() { if (this.container !== global) { this.dom = document.querySelector(this.container) } else { this.dom = this.container } if (!this.dom) { return } this.dom.addEventListener('scroll', this.scroll, false) if (this.autoload && !this.loading) { this.load() } }, methods: { /** * 滚动钩子 */ scroll() { if (!this.loading) { return } const viewHeight = global.innerHeight let parentNode if (this.container !== global) { parentNode = this.$el } else { parentNode = this.$el.parentNode } if (parentNode) { const rect = parentNode.getBoundingClientRect() if ((rect.bottom <= viewHeight + this.distance)) { this.load() } } }, /** * 加载一组数据的方法 */ load() { this.$emit('load') }, }, beforeDestroy() { if (this.dom) { this.dom.removeEventListener('scroll', this.scroll, false) } }, } </script> <style lang="stylus" scoped> .loadmore { user-select: none; &__footer { color: #e4e4e4; padding: 20px; text-align: center; } .tc-loading { ~ span { vertical-align: middle; } } } </style>