原來vue的slot可以這麼玩轉

大雄沒了哆啦A夢發表於2018-09-21

vue的內容分發非常適合“固定部分+動態部分”的元件的場景,固定部分可以是結構固定,也可以是邏輯固定,比如下拉loading,下拉loading只是中間內容是動態的,而拉到底部都會觸發拉取更多內容的操作,因此我們可以把下拉loading做成一個有slot的外掛。

使用場景

“下拉載入更多”的場景在移動端相對來說出現得比較多。我們知道下拉觸底都要監聽觸底事件,觸底的操作也相同(去後臺拉取資料),分頁演算法也相同,因此我們會想到把它做成一個元件,重用這些相同的地方,讓其他地方可以共用這個元件,從而減少程式碼量。
然而,下拉loading並不是一個可以完全重用的元件,因為列表裡面的內容不同,空白頁(沒有內容時)的內容也可能不同,如果要做成元件,那麼就要考慮到這方面的“不同”,因此我們想到利用vue的內容分發slot來做。下面是本人在開發的時候做的一個下拉loading,大家可以參考下。

元件程式碼

<template>
  <div>
    <slot name="list" v-if="total > 0"></slot>
    <slot name="empty" v-else></slot>
  </div>
</template>
<script>
import Toast from 'lib/xl-toast'

import Tool from 'tool/tool'

export default {
  data() {
    return {
      page: 1,
      isLoading: false,
      busy: false,
      isFirstLoad: false
    }
  },
  props: {
    pageSize: {
      default: 10 // 每頁展示多少條資料
    },
    total: {
      default: 0 // 總共多少條記錄
    }
  },
  computed: {
    totalPage() {
      return Math.ceil(this.total / this.pageSize)
    }
  },
  created() {
    this.getList()
  },
  mounted() {
    this.addScrollListener()
  },
  methods: {
    addScrollListener() {
      // 新增監聽滾動操作,用到函式防抖
      this.scrollFn = Tool.throttle(this.onScroll, 30, 30)
      document.addEventListener('scroll', this.scrollFn, false)
    },
    getList() {
      // 正在拉取資料或者沒有資料了,則取消滾動監聽
      if(this.isLoading || this.isFirstLoad && (this.page > this.totalPage)) {
        document.removeEventListener('scroll', this.scrollFn, false)
        return
      }
      this.busy = true
      this.isLoading = true
      // 通知父元件去拉取更多資料
      this.$emit("getList", this.page, () => {
        this.isFirstLoad = true
        this.isLoading = false
        this.page++
      }, () => {
        Toast.show('網路錯誤,請稍後重試')
        this.total = 0
        this.isLoading = false
      })
    },
    reset() {
      // 重新拉取資料
      this.page = 1
      this.total = 0
      this.isLoading = false
      this.isFirstLoad = false
      this.addScrollListener()
      this.getList()
    },
    onScroll() {
      // 到底拉取更多資料 
      if(Tool.touchBottom()) {
        this.getList()
      }
    }
  }
}
</script>

複製程式碼

總之,遇到一些有想對比較固定的部分,包括js操作或者結構固定,又有一些動態的部分,我們應該就應該考慮到使用:元件+slot。

意向不到的slot另類用法

我在做需求的時候,做了一個元件,該元件分為上下兩個部分,這兩個部分耦合度很高(不然我怎麼把它當成一個元件呢哈哈哈),如下圖所示:

原來vue的slot可以這麼玩轉
本來C區域是一個元件,然後產品突然說,需要把這兩個部分分開,把A移到C1的位置,C1移到A的位置(心裡感覺到憋屈)。
這裡我的第一個想法就是拆開來做成兩個元件,但是問題來了,之前這兩部分的耦合度很高,如果強制把它拆開成兩個元件,那麼這兩個元件之間的互動必然會多很多。比如,C1改變了某個東西會影響到C2,那麼C1需要觸發事件通知父元件,父元件再呼叫C2的某個方法來更新狀態。這種跨元件之間的通訊在元件之間頻繁互動的情況下,將會是噩夢,而我這邊卻需要頻繁的互動,所以如果把它拆分為兩個元件,那麼工作量和複雜度將會大大的增加。當然,你可以想到通過Event Hub的方式來實現兩個元件之間的互動,但是根本問題還是沒有實質性得得到解決。
那麼,有什麼方法可以做到不拆分成兩個元件又能移動位置的方法呢,答案就是slot。以我的例子為例,把A和B作為C的內容分發,原來是這樣的:

<A></A>
<B></B>
<C></C>
複製程式碼

改為slot以後是這樣的

<C>
<A slot="c1"></A>
<B slot="c2"></B>
</C>
複製程式碼

這樣就能做到不把C模組拆分,又能調整位置了,以最小的代價完成需求~~。

總結

vue的slot不僅可以用來內容分發,還可以用來做位置調整。如果在需要拆分元件來做位置調整,又不想因為拆分耦合度很高的元件,可以考慮使用slot來進行位置調整。一點愚見,希望對大家有所幫助。

相關文章