Mark Ku's Blog
首頁 關於我
Vue 全域 / 非全域 Loading 的等待特效 ( 支援多個併發請求 )
Frontend
Vue 全域 / 非全域 Loading 的等待特效 ( 支援多個併發請求 )
Mark Ku
Mark Ku
September 21, 2021
1 min

Vue 全域 / 非全域 Loading 的等待特效 ( 支援多個併發請求 )

解決問題

在前端工程中,常常會透過 ajax 向後端做非同步資料交換,但api 可能會因各種因素延遲回應,為提昇更好的使用者體驗,通常都會做一個過場的動畫,讓使用者得知正在加載資料。

而大多數套件設計的相當簡易,背後可能只是一個 boolean 值,在多個併發的非同步的ajax request ,只要有任一個 request,提前回來就被關掉 loading 的過場動畫。

設計原理

在 vuex 中 做個計數器功能, ajax 發出 request 時,就呼叫 store 中的increment方法,讓count 就加1,完成或失敗則 count 減1,並提供 isLoading 的 getter 給畫面判斷, 透過封裝 axios 來由外部傳入參數決定需不需要 show loading 轉場動畫。

  1. request 進來 就 count + 1
  2. request 失敗 count - 1 ( 參數錯誤,不會往下走,也不會走到 reponse )
  3. response 成功 count - 1
  4. response 失敗 count - 1 ( time out 、 server error、 cancel )

Vuex 的程式碼

import { generatorUUID } from '@/utils'
var _ = require('lodash')

const state = {

  count: []
}
// getters
const getters = {
  isLoading: (state) => {
    return state.count.length > 0
  },
  removeByItems: (state) => {
    return state.count.filter(x => x.isRemoveByRequestId === true)
  }
}
// mutations
const mutations = {
  increment(state, requestId) {
    const isRemoveByRequestId = requestId !== '' && requestId !== undefined

    requestId = requestId || generatorUUID()

    var newLoading = { requestId: requestId, isRemoveByRequestId: isRemoveByRequestId }

    state.count.push(newLoading)
  },
  decrement(state, requestId) {
    if (requestId) {
      var index = state.count.findIndex(item => item.requestId === requestId)
      state.cart.splice(index, 1)
    } else {
      const newCount = _.cloneDeep(state.count).filter(x => x.isRemoveByRequestId === false)
      if (newCount.length > 0) {
        newCount.shift()
        state.count = newCount
      }
    }
  }
}
// actions
const actions = {

}
export default {
  namespaced: true,
  state,
  getters,
  actions,
  mutations
}

封裝 axios

function apiAxios(method, url, params, hideLoading, requestId) {
  // 顯示loading
  const appParams = {}
  params = { ...appParams, ...params }
  const httpDefault = {
    method: method,
    baseURL: baseURL,
    url: url,
    hideLoading: hideLoading,
    requestId: requestId,
    // `params` 是即將與請求一起發送的 URL 參數
    // `data` 是作為請求主體被發送的數據
    params: method === 'GET' || method === 'DELETE' ? params : null,
    data: method === 'POST' || method === 'PUT' ? params : null,
    timeout: 100000
  }

  if (httpDefault['hideLoading'] === false) {
    store.commit('loading/increment', httpDefault['requestId'])
  }

  return new Promise((resolve, reject) => {
    axios(httpDefault)
      .then((res) => {
        if (httpDefault['hideLoading'] === false) {
          store.commit('loading/decrement', requestId)
        }
        resolve(successState(res))
      })
      .catch((response) => {
        if (httpDefault['hideLoading'] === false) {
          store.commit('loading/decrement', requestId)
        }
       
        errorState(response)
      })
  })
}

在 Layout 加上 Loading box 的組件,並 監聽 getter 中的 isLoading 屬性,來顯示 Loading box 。

倘若 Call Api 時不需要顯示 loading 時,則多傳遞一個 true 的參數

Vue.prototype.$getAxios(${process.env.VUE_APP_BASE_API}/api/v1/home/query, null, true)

為了之後擴充,預留一個彈性,各別元件若需要控制,可以由外面把 request id 傳入,元件在去 watch loading count 物件中 isRemoveByRequestId 屬性的資料

延伸議題 - Skeleton

現今越來越多網站 採用 Skeleton 提昇使用者的使用體驗,可以之後列成優化項目。

參考資料連結


Tags

Mark Ku

Mark Ku

Software Developer

9年以上豐富網站開發經驗,開發過各種網站,直播、POS系統、電子商務、平台網站、SEO、金流串接、AI整合、Infra 出身,帶過幾次團隊,目前專注於北美及德國市場電商網站開發團隊

Expertise

前端(React)
後端(C#)
網路管理
DevOps
溝通
領導

Social Media

facebook github website

Related Posts

Vue 透過 include 及 Vuex 解決 keepAlive 難解的記憶體釋放問題
Vue 透過 include 及 Vuex 解決 keepAlive 難解的記憶體釋放問題
May 05, 2022
1 min

Quick Links

關於我

Social Media