如何寫(xiě)一個(gè)vue公共組件

前言

一個(gè)前端小開(kāi)發(fā)仔,封裝一個(gè)適銷對(duì)路的組件,是我們必備的基本功。
本文以實(shí)現(xiàn)一個(gè)vue的滾動(dòng)監(jiān)聽(tīng)組件,總結(jié)一下如何封裝一個(gè)造福后人(能用)的公共組件。

vue.png

需求場(chǎng)景

H5應(yīng)用中,為了減輕后臺(tái)查詢數(shù)據(jù)的壓力,列表常以滾動(dòng)加載的形式實(shí)現(xiàn)。
即首次進(jìn)入頁(yè)面時(shí),只請(qǐng)求若干條數(shù)據(jù)。當(dāng)頁(yè)面滾動(dòng)到底部時(shí),再次請(qǐng)求接口返回?cái)?shù)據(jù)。

需求分析

由于這種業(yè)務(wù)場(chǎng)景十分常見(jiàn),因此可封裝一個(gè)通用的滾動(dòng)監(jiān)聽(tīng)組件,監(jiān)聽(tīng)頁(yè)面滾動(dòng)的位置。當(dāng)觸發(fā)特定條件時(shí),向外派發(fā)一個(gè)事件。

功能實(shí)現(xiàn)

頁(yè)面滾動(dòng)監(jiān)聽(tīng)

  1. 當(dāng)父元素的固定高度 < 內(nèi)部子元素的總高度,且父元素 overflow: scroll 時(shí),可以監(jiān)聽(tīng)到父元素的 scroll 事件。因此,滾動(dòng)列表需要插在scroll里。這里我們需要用到一個(gè) slot 來(lái)實(shí)現(xiàn)。
  2. 由于需要綁定DOM事件,需要獲得對(duì)DOM的控制。這里我們需要用到一個(gè) refs 來(lái)實(shí)現(xiàn)。
  3. 當(dāng)觸發(fā)監(jiān)聽(tīng)的條件時(shí),我們需要對(duì)調(diào)用scroll組件的組件派發(fā)一個(gè)事件,以通知進(jìn)行下一步的邏輯。這里我們需要用到一個(gè) emit 來(lái)實(shí)現(xiàn)。

至此,一個(gè)滾動(dòng)組件的主要功能就出來(lái)了。但是,作為一個(gè)有追求的組件,我們是不是還可以改進(jìn)一下呢。

優(yōu)化

防抖

滾動(dòng)事件觸發(fā)頻率很高,因此可以做一個(gè)防抖操作,只在最后一次滾動(dòng)事件觸發(fā)時(shí)做判斷滾動(dòng)位置的操作。

綁定移除

當(dāng)后臺(tái)無(wú)相關(guān)數(shù)據(jù)時(shí),就不需要再監(jiān)聽(tīng)滾動(dòng)事件,寫(xiě)一個(gè)解除綁定的方法,以供按需調(diào)用。

解決慣性滾動(dòng)問(wèn)題

手機(jī)實(shí)測(cè)時(shí),滾動(dòng)事件只在手指滑動(dòng)接觸屏幕時(shí)觸發(fā)滾動(dòng)事件。因此,當(dāng)判定未到達(dá)底部時(shí),寫(xiě)一個(gè)延時(shí)判斷邏輯,判斷是否在慣性滾動(dòng)其間,元素滾動(dòng)到底部。(各位大佬有其他解決辦法,可以評(píng)論提一下)

注:記得添加 -webkit-overflow-scrolling: touch; 否則IOS端不實(shí)現(xiàn)慣性滾動(dòng)

增加通用性

當(dāng)前設(shè)計(jì)下,觸發(fā)派發(fā)事件的滾動(dòng)位置、防抖的間隔、refs的值都是固定的。

但對(duì)于不同列表,可能需要提早做一些預(yù)加載數(shù)據(jù)、操作一些滾動(dòng)組件的DOM等。

因此這些常用的參數(shù),應(yīng)該通過(guò) props 對(duì)外暴露出來(lái),讓父組件傳入。

如果定義了一個(gè)是Object類型的props,可能會(huì)出現(xiàn)某些字段不傳的情況。

這時(shí),組件里利用 三元表達(dá)式 ,Object.key ? Object.key : defaultValue ,可使得組件有良好的默認(rèn)表現(xiàn)。

效果如下:

滾動(dòng)組件效果.gif

demo地址:https://github.com/yejiayuan163/simpleScroll
滾動(dòng)組件代碼見(jiàn)文末

總結(jié)

寫(xiě)完一個(gè)滾動(dòng)組件的開(kāi)發(fā)流程,現(xiàn)在可以總結(jié)一下開(kāi)發(fā)一個(gè)vue公共組件的原則

  1. 只關(guān)注公共組件自身的功能。 公共組件只關(guān)注自己的狀態(tài),不關(guān)心頁(yè)面數(shù)據(jù)邏輯。
  2. 設(shè)計(jì)好與父組件的通信。 父組件通過(guò)props控制公共組件的表現(xiàn)。通過(guò)emit的事件,監(jiān)聽(tīng)到公共組件狀態(tài)。通過(guò)refs調(diào)用到公共組件的方法。當(dāng)然用vuex+computed屬性監(jiān)聽(tīng)也是可以的。
  3. 有良好的默認(rèn)表現(xiàn) 使用者既可定制組件的表現(xiàn),也可以簡(jiǎn)單地使用默認(rèn)配置。
  4. 記得寫(xiě)使用文檔。

謝謝觀看!

附?代碼實(shí)現(xiàn)

<!--調(diào)用時(shí),flbScroll的父元素需要固定高度,flbScroll包裹的元素(需要被監(jiān)聽(tīng)的元素),高度不可固定,需要由內(nèi)部元素?fù)伍_(kāi)-->
<!--移動(dòng)端使用,故未對(duì)ie8做兼容處理-->
<template>
    <div class="scrollWrapper" :ref="wrapperName">
        <slot></slot>
    </div>
</template>

<script>
  export default {
    name: 'y-scroll',
    props:{
      // 被監(jiān)聽(tīng)元素距離底部的距離
      disFromBottom:{
        type: Number,
        default: 300
      },
      // 被監(jiān)聽(tīng)元素距離頂部的距離
      disFromTop:{
        type: Number,
        default: 300
      },
      // 給scroll組件外層定義一個(gè)ref,用于獲取監(jiān)聽(tīng)滾動(dòng)
      wrapperName:{
        type: String,
        default: '1'
      },
      //觸發(fā)判斷的時(shí)間間隔
      interval: {
        type: Number,
        default: 300
      },
    },
    data() {
      return {
        lastTime: 0,
        timer:''
      }
    },
    mounted() {
      // 監(jiān)聽(tīng)滾動(dòng)事件
      this.$nextTick(()=> {
        this.$refs[this.wrapperName].addEventListener('scroll', this.listenPosition, true);
      })
    },
    methods:{
      listenPosition(){
        if(this.lastTime + this.interval < new Date().getTime()){
          this.lastTime = new Date().getTime()
          const scrollTop = this.$refs[this.wrapperName].scrollTop//卷去的高度
          const scrollHeight = this.$refs[this.wrapperName].scrollHeight//正文全文高
          const height = this.$refs[this.wrapperName].clientHeight//元素的高度
          // console.log('scrollTop:',scrollTop,'scrollHeight:',scrollHeight,'height:',height)
          if(scrollHeight <= scrollTop + height + this.disFromBottom){
            console.log('到底了?。?)
            if(this.timer){
              clearTimeout(this.timer)
            }
            this.$emit('onBottom')
          } else {
            // 解決慣性滾動(dòng)到底部不觸發(fā)的問(wèn)題
            this.timer=setTimeout(() => {
              const scrollTop = this.$refs[this.wrapperName].scrollTop//卷去的高度
              const scrollHeight = this.$refs[this.wrapperName].scrollHeight//正文全文高
              const height = this.$refs[this.wrapperName].clientHeight//元素的高度
              if(scrollHeight <= scrollTop + height + this.disFromBottom){
                this.$emit('onBottom')
              }
            },1000)
          }
        }
      },
      //解除滾動(dòng)綁定
      unbindScroll(){
        this.$refs[this.wrapperName].removeEventListener('scroll', this.listenPosition, true);//第三個(gè)參數(shù)布爾值與綁定時(shí)要一致,第二個(gè)參數(shù)不能用匿名函數(shù)
        console.log('解綁滾動(dòng)事件')
      }
    },
    beforeDestroy(){
      // 移除監(jiān)聽(tīng)滾動(dòng)事件
      this.$refs[this.wrapperName].removeEventListener('scroll', this.listenPosition, true);
    }
  }
</script>

<style scoped>
    .scrollWrapper{
        height: 100%;
        overflow: scroll;
        -webkit-overflow-scrolling: touch;
    }
</style>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容