一起寫一個即插即用的Vue Loading插件實(shí)現(xiàn)
無論最終要實(shí)現(xiàn)怎樣的網(wǎng)站,Loading狀態(tài)都是必不可少的一環(huán),給用戶一個過渡喘息的機(jī)會也給服務(wù)器一個遞達(dá)響應(yīng)的時間。
從使用方式說起
不管從0開始寫起還是直接下載的Loading插件,都會抽象為一個組件,在用到的時候進(jìn)行加載Loading,或者通過API手動進(jìn)行show或者h(yuǎn)ide
<wait>
</wait>
...
this.$wait.show()
await fetch('http://example.org')
this.$wait.hide()
或者通過Loading狀態(tài)進(jìn)行組件間的切換
<loader v-if="isLoading"> </loader> <Main v-else> </Main>
。要想注冊成全局狀態(tài),還需要給axios類的網(wǎng)絡(luò)請求包添加攔截器,然后設(shè)置一個全局Loading狀態(tài),每次有網(wǎng)絡(luò)請求或者根據(jù)已經(jīng)設(shè)置好的URL將Loading狀態(tài)設(shè)置為加載,請求完成后在設(shè)置為完成。
注冊axios攔截器:
let loadingUrls = [
`${apiUrl}/loading/`,
`${apiUrl}/index/`,
`${apiUrl}/comments/`,
...
]
axios.interceptors.request.use((config) => {
let url = config.url
if (loadingUrls.indexOf('url') !== -1) {
store.loading.isLoading = true
}
})
axios.interceptors.response.use((response) => {
let url = response.config.url
if (loadingUrls.indexOf('url') !== -1) {
store.loading.isLoading = false
}
})
使用時在每個組件下獲取出loading狀態(tài),然后判斷什么時候顯示loading,什么時候顯示真正的組件。
<template>
<div>
<loader v-if="isLoading">
</loader>
<Main v-else>
</Main>
</div>
</template>
<script>
...
components: {
loader
},
computed: {
isLoading: this.$store.loading.isLoading
},
async getMainContent () {
// 實(shí)際情況下State僅能通過mutations改變.
this.$sotre.loading.isLoading = false
await axios.get('...')
this.$sotre.loading.isLoading = false
},
async getMain () {
await getMainContent()
}
...
</script>
在當(dāng)前頁面下只有一個需要Loading的狀態(tài)時使用良好,但如果在同一個頁面下有多個不同的組件都需要Loading,你還需要根據(jù)不同組件進(jìn)行標(biāo)記,好讓已經(jīng)加載完的組件不重復(fù)進(jìn)入Loading狀態(tài)...隨著業(yè)務(wù)不斷增加,重復(fù)進(jìn)行的Loading判斷足以讓人煩躁不已...
整理思路
Loading的核心很簡單,就是請求服務(wù)器時需要顯示Loading,請求完了再還原回來,這個思路實(shí)現(xiàn)起來并不費(fèi)力,只不過使用方式上逃不開上面的顯式調(diào)用的方式。順著思路來看,能進(jìn)行Loading設(shè)置的地方有,
- 設(shè)置全局?jǐn)r截,請求開始前設(shè)置狀態(tài)為加載。
- 設(shè)置全局?jǐn)r截,請求結(jié)束后設(shè)置狀態(tài)為完成。
- 在觸發(fā)請求的函數(shù)中進(jìn)行攔截,觸發(fā)前設(shè)置為加載,觸發(fā)后設(shè)置為完成。
- 判斷請求后的數(shù)據(jù)是否為非空,如果非空則設(shè)置為完成
最終可以實(shí)現(xiàn)的情況上,進(jìn)行全局?jǐn)r截設(shè)置,然后局部的判斷是最容易想到也是最容易實(shí)現(xiàn)的方案。給每個觸發(fā)的函數(shù)設(shè)置before和after看起來美好,但實(shí)現(xiàn)起來簡直是災(zāi)難,我們并沒有before和after這兩個函數(shù)鉤子來告訴我們函數(shù)什么時候調(diào)用了和調(diào)用完了,自己實(shí)現(xiàn)吧坑很多,不實(shí)現(xiàn)吧又沒得用只能去原函數(shù)里一個個寫上。只判斷數(shù)據(jù)局限性很大,只有一次機(jī)會。
既然是即插即用的插件,使用起來就得突出一個簡單易用,基本思路上也是使用全局?jǐn)r截,但局部判斷方面與常規(guī)略有不同,使用數(shù)據(jù)綁定(當(dāng)然也可以再次全局響應(yīng)攔截),咱們實(shí)現(xiàn)起來吧~。
樣式
Loading嘛,必須得有一個轉(zhuǎn)圈圈才能叫Loading,樣式并不是這個插件的最主要的,這里直接用CSS實(shí)現(xiàn)一個容易實(shí)現(xiàn)又不顯得很糙的:
<template>
<div class="loading">
</div>
</template>
...
<style scoped>
.loading {
width: 50px;
height: 50px;
border: 4px solid rgba(0,0,0,0.1);
border-radius: 50%;
border-left-color: red;
animation: loading 1s infinite linear;
}
@keyframes loading {
0% { transform: rotate(0deg) }
100% { transform: rotate(360deg) }
}
</style>
固定大小50px的正方形,使用border-radius把它盤得圓潤一些,border設(shè)置個進(jìn)度條底座,border-left-color設(shè)置為進(jìn)度條好了。

綁定數(shù)據(jù)與URL
提供外部使用接口
上面思路中提到,這個插件是用全局?jǐn)r截與數(shù)據(jù)綁定制作的:
- 暴露一個 source 屬性,從使用的組件中獲取出要綁定的數(shù)據(jù)。
- 暴露一個 urls 屬性,從使用的組件中獲取出要攔截的URL。
<template>
...
</template>
<script>
export default {
props: {
source: {
require: true
},
urls: {
type: Array,
default: () => { new Array() }
}
},
data () {
return { isLoading: true }
},
watch: {
source: function () {
if (this.source) {
this.isLoading = false
}
}
}
}
</script>
<style scoped>
....
</style>
不用關(guān)心source是什么類型的數(shù)據(jù),我們只需要監(jiān)控它,每次變化時都將Loading狀態(tài)設(shè)置為完成即可,urls我們稍后再來完善它。
設(shè)置請求攔截器
攔截器中需要的操作是將請求時的每個URL壓入一個容器內(nèi),請求完再把它刪掉。
Vue.prototype.__loader_checks = []
Vue.prototype.$__loadingHTTP = new Proxy({}, {
set: function (target, key, value, receiver) {
let oldValue = target[key]
if (!oldValue) {
Vue.prototype.__loader_checks.forEach((func) => {
func(key, value)
})
}
return Reflect.set(target, key, value, receiver)
}
})
axios.interceptors.request.use(config => {
Vue.prototype.$__loadingHTTP[config.url] = config
return config
})
axios.interceptors.response.use(response => {
delete Vue.prototype.$__loadingHTTP[response.config.url]
return response
})
將其掛載在Vue實(shí)例上,方便我們之后進(jìn)行調(diào)用,當(dāng)然還可以用Vuex,但此次插件要突出一個依賴少,所以Vuex還是不用啦。
直接掛載在Vue上的數(shù)據(jù)不能通過computed或者watch來監(jiān)控數(shù)據(jù)變化,咱們用Proxy代理攔截set方法,每當(dāng)有請求URL壓入時就做點(diǎn)什么事。Vue.prototype.__loader_checks用來存放哪些實(shí)例化出來的組件訂閱了請求URL時做加載的事件,這樣每次有URL壓入時,通過Proxy來分發(fā)給訂閱過得實(shí)例化Loading組件。

訂閱URL事件
<template>
...
</template>
<script>
export default {
props: {
source: {
require: true
},
urls: {
type: Array,
default: () => { new Array() }
}
},
data () {
return { isLoading: true }
},
watch: {
source: function () {
if (this.source) {
this.isLoading = false
}
}
},
mounted: function () {
if (this.urls) {
this.__loader_checks.push((url, config) => {
if (this.urls.indexOf(url) !== -1) {
this.isLoading = true
}
})
}
}
}
</script>
<style scoped>
....
</style>
每一個都是一個嶄新的實(shí)例,所以直接在mounted里訂閱URL事件即可,只要有傳入urls,就對__loader_checks里每一個訂閱的對象進(jìn)行發(fā)布,Loader實(shí)例接受到發(fā)布后會判斷這個URL是否與自己注冊的對應(yīng),對應(yīng)的話會將自己的狀態(tài)設(shè)置回加載,URL請求后勢必會引起數(shù)據(jù)的更新,這時我們上面監(jiān)控的source就會起作用將加載狀態(tài)設(shè)置回完成。

使用槽來適配原來的組件
寫完上面這些你可能有些疑問,怎么將Loading時不應(yīng)該顯示的部分隱藏呢?答案是使用槽來適配,
<template>
<div>
<div class="loading" v-if="isLoading" :key="'loading'">
</div>
<slot v-else>
</slot>
</div>
</template>
<script>
export default {
props: {
source: {
require: true
},
urls: {
type: Array,
default: () => { new Array() }
}
},
data () {
return { isLoading: true }
},
watch: {
source: function () {
if (this.source) {
this.isLoading = false
}
}
},
mounted: function () {
if (this.urls) {
this.__loader_checks.push((url, config) => {
if (this.urls.indexOf(url) !== -1) {
this.isLoading = true
}
})
}
}
}
</script>
<style scoped>
....
</style>
還是通過isLoading判斷,如果處于加載那顯示轉(zhuǎn)圈圈,否則顯示的是父組件里傳入的槽,
這里寫的要注意,Vue這里有一個奇怪的BUG,
<div class="loading" v-if="isLoading" :key="'loading'"> </div> <slot v-else> </slot>
在有<slot>時,如果同級的標(biāo)簽同時出現(xiàn)v-if與CSS選擇器且樣式是scoped,那用CSS選擇器設(shè)置的樣式將會丟失,<div v-if="isLoading" :key="'loading'">如果沒有設(shè)置key那.loading的樣式會丟失,除了設(shè)置key還可以把它變成嵌套的<div v-if="isLoading"> <div></div> </div>。
注冊成插件
Vue中的插件有四種注冊方式,這里用mixin來混入到每個實(shí)例中,方便使用,同時我們也把上面的axios攔截器也注冊在這里。
import axios
import Loader from './loader.vue'
export default {
install (Vue, options) {
Vue.prototype.__loader_checks = []
Vue.prototype.$__loadingHTTP = new Proxy({}, {
set: function (target, key, value, receiver) {
let oldValue = target[key]
if (!oldValue) {
Vue.prototype.__loader_checks.forEach((func) => {
func(key, value)
})
}
return Reflect.set(target, key, value, receiver)
}
})
axios.interceptors.request.use(config => {
Vue.prototype.$__loadingHTTP[config.url] = config
return config
})
axios.interceptors.response.use(response => {
delete Vue.prototype.$__loadingHTTP[response.config.url]
return response
})
Vue.mixin({
beforeCreate () {
Vue.component('v-loader', Loader)
}
})
}
}
使用
在入口文件中使用插件
import Loader from './plugins/loader/index.js' ... Vue.use(Loader) ...
任意組件中無需導(dǎo)入即可使用
<v-loader :source="msg" :urls="['/']">
<div @click="getRoot">{{ msg }}</div>
</v-loader>
根據(jù)綁定的數(shù)據(jù)和綁定的URL自動進(jìn)行Loading的顯示與隱藏,無需手動設(shè)置isLoading是不是該隱藏,也不用調(diào)用show與hide在請求的方法里打補(bǔ)丁。
其他
上面的通過綁定數(shù)據(jù)來判斷是否已經(jīng)響應(yīng),如果請求后的數(shù)據(jù)不會更新,那你也可以直接在axios的response里做攔截進(jìn)行訂閱發(fā)布模式的響應(yīng)。
最后
咳咳,又到了嚴(yán)(hou)肅(yan)認(rèn)(wu)真(chi)求Star環(huán)節(jié)了,附上完整的項(xiàng)目地址(我不會告訴你上面的測試地址里的代碼也很完整的,絕不會!)。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- vue 全局封裝loading加載教程(全局監(jiān)聽)
- 基于vue+element實(shí)現(xiàn)全局loading過程詳解
- vuex+axios+element-ui實(shí)現(xiàn)頁面請求loading操作示例
- Vue全局loading及錯誤提示的思路與實(shí)現(xiàn)
- 詳解Vue項(xiàng)目中出現(xiàn)Loading chunk {n} failed問題的解決方法
- vue 使用vant插件做tabs切換和無限加載功能的實(shí)現(xiàn)
- vue使用video插件vue-video-player詳解
- vue使用video插件vue-video-player的示例
- vue使用screenfull插件實(shí)現(xiàn)全屏功能
- 最全vue的vue-amap使用高德地圖插件畫多邊形范圍的示例代碼
- 寫一個Vue loading 插件
相關(guān)文章
vue3.0響應(yīng)式函數(shù)原理詳細(xì)
這篇文章主要介紹了vue3.0響應(yīng)式函數(shù)原理,Vue3的響應(yīng)式系統(tǒng)可以監(jiān)聽動態(tài)添加的屬性還可以監(jiān)聽屬性的刪除操作,以及數(shù)組的索引以及l(fā)ength屬性的修改操作。另外Vue3的響應(yīng)式系統(tǒng)還可以作為模塊單獨(dú)使用。下面更多介紹,需要的小伙伴可以才可以參考一下2022-02-02
Vue中的el-date-picker時間選擇器的使用實(shí)例詳解
el-date-picker是Element UI框架中提供的日期選擇器組件,它支持單個日期、日期范圍、時間、日期時間等多種選擇方式,本文給大家介紹Vue中的el-date-picker時間選擇器的使用,感興趣的朋友一起看看吧2023-10-10
Vue3配置路由ERROR in [eslint]報錯問題及解決
這篇文章主要介紹了Vue3配置路由ERROR in [eslint]報錯問題及解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-10-10
axios?發(fā)?post?請求,后端接收不到參數(shù)的完美解決方案
這篇文章主要介紹了axios?發(fā)?post?請求,后端接收不到參數(shù)的解決方案,場景很簡單,就是一個正常 axios post 請求,本文給大家分享問題原因分析及解決方案需要的朋友可以參考下2022-12-12

