隊列在前端彈窗中的應用

前端開發中,若是遇到複雜的交互邏輯,數據結構的知識將幫助你理清思路,抽象邏輯,完成穩定可靠的邏輯代碼。前端

本文就講講我在開發彈窗時加入的隊列數據結構,也許有人疑問彈窗不是很簡單嗎,還須要引入隊列?其實在複雜交互中,特別是互動類的界面中,很容易就會有超過 10 個彈窗對話框,萬一同時被觸發時,邏輯就會混亂,咱們但願一個接一個的方式彈出,這裏就須要隊列了。後端

什麼是隊列

隊列(Queue) 是先進先出(FIFO, First-In-First-Out)的線性表。在具體應用中一般用鏈表或者數組來實現。隊列只容許在尾部進行插入操做(入隊 enqueue),在頭部進行刪除操做(出隊 dequeue)。隊列的操做方式和堆棧相似,惟一的區別在於隊列只容許新數據在後端進行添加。上圖清晰的描述了隊列的特性。api

JavaScript 實現隊列

一個隊列數據結構要包含如下 api數組

  • 入隊
  • 出隊
  • 獲取頭部元素
  • 獲取尾部元素
  • 隊列是否爲空

使用 JavaScript/TypeScript 數組能夠模擬出這些 api,代碼以下數據結構

class Queue {
  private dataStore: any[]

  constructor() {
    this.dataStore = []
  }

  public enqueue(e: any): void {
    this.dataStore.push(e)
  }

  public dequeue() {
    this.dataStore.shift()
  }

  public front() {
    return this.dataStore[0]
  }

  public back() {
    return this.dataStore[this.dataStore.length - 1]
  }

  public isEmpty(): boolean {
    if (this.dataStore.length === 0) {
      return true
    }
    return false
  }

  public toString() {
    return this.dataStore.join(',')
  }
}

export default Queue
複製代碼

能夠寫些測試用例來驗證這個隊列的功能,保證咱們的隊列運行正常ide

import Queue from './Queues'

describe('Queue', () => {
  const q = new Queue()
  q.enqueue(1123)
  q.enqueue('chuanshi')
  q.enqueue('666')
  test('queue', () => {
    expect(q.toString()).toBe('1123,chuanshi,666')
    q.dequeue()
    expect(q.toString()).toBe('chuanshi,666')
    expect(q.front()).toBe('chuanshi')
    expect(q.back()).toBe('666')
    expect(q.isEmpty()).toBe(false)
    q.dequeue()
    q.dequeue()
    expect(q.isEmpty()).toBe(true)
  })
})
複製代碼

這樣咱們就獲得了一個隊列的數據結構。oop

對話框彈窗(Dialog)與隊列的結合

彈窗被觸發喚起會有如下3種狀況:測試

  1. 同一時間段只有一個 Dialog 被觸發
  2. 同一時間段有2個 Dialog 同時被觸發
  3. Dialog 正在展現時,又觸發了另外一個 Dialog

爲了知足以上3種狀況,須要在主邏輯和彈窗展現之間加一個隊列控制邏輯,它們的時序圖以下:ui

上述時序圖清晰的表達了彈窗觸發的各類狀況,那麼起到關鍵做用的隊列控制(QueueCtrl)部分應該如何編寫呢?其實也很簡單,它的邏輯以下:this

當空隊列的第一個元素入隊後,上圖的右側循環部分開始啓動,同時依然能夠有元素入隊,直到右側循環邏輯將隊列全部元素出隊後,整個活動中止。

核心代碼以下:

import Queue from './Queues'

const queue = new Queue() // 實例化上文寫好的隊列類

/** * 將彈窗事件名推入隊列 */
const push = (eventName: globalEventName) => {
  if (queue.isEmpty()) {
    queue.enqueue(eventName)
    openDialog() // 啓動出隊邏輯
  } else {
    queue.enqueue(eventName) // 循環中依然能夠同時入隊新的元素
  }
}

/** * 打開彈窗,遞歸,循環出隊 */
const openDialog = () => {
  // 打開彈窗
  document.dispatchEvent(new Event(queue.front()))

  // 監聽彈窗關閉
  document.addEventListener(`${queue.front()}Close`, () => {
    queue.dequeue() // 出隊
    if (!queue.isEmpty()) { // 隊列不爲空時,遞歸
      openDialog()
    }
  })
}

export default {
  push,
}
複製代碼

只須要調用 push() 就能夠達到咱們的目的,能夠看到使用隊列這種數據結構,不到20行代碼,很是簡潔優雅的解決了這個問題!

例如這樣調用時

DialogQueue.push(globalEventName.dialogReceiveCoinsDaily)
setTimeout(() => {
  DialogQueue.push(globalEventName.dialogWinLottery)
}, 1000)
setTimeout(() => {
  DialogQueue.push(globalEventName.dialogReceiveCoinsDaily)
}, 1500)
setTimeout(() => {
  DialogQueue.push(globalEventName.dialogWinLottery)
}, 2000)
複製代碼

會如下面的方式按順序彈出

Toast 與隊列的結合

與 Dialog 相似,爲了不屢次觸發致使的 Toast 堆疊,把每個要彈出的 Toast 內容入隊,每次彈出隊首的 Toast,每一個 Toast 完成時,出隊,並遞歸調用展現,直到隊列內容爲空。這裏不展開說明了。

例如這樣調用時

Toast.show('hi1')
Toast.show('hi2')
Toast.show('hi3')
setTimeout(() => {
  Toast.show('hi5')
}, 3000);
setTimeout(() => {
  Toast.show('hi4')
}, 2000);
複製代碼

會如下面的方式按順序彈出

小結

固然上面的需求不使用隊列也能夠實現,可是隊列數據結構的意義在於可讓整個實現更加規範化、抽象化且易於維護。

熟練掌握數據結構的知識,可讓開發的過程當中思路更加清晰,代碼抽象化程度更高,更加合理的組織代碼,提升開發效率。當遇到棘手的問題時,能夠多思考一些數據結構中的知識點,說不定能夠達到事半功倍的效果呢!

相關文章
相關標籤/搜索