一起寫一個(gè)即插即用的Vue Loading插件實(shí)現(xiàn)
無(wú)論最終要實(shí)現(xiàn)怎樣的網(wǎng)站,Loading狀態(tài)都是必不可少的一環(huán),給用戶一個(gè)過(guò)渡喘息的機(jī)會(huì)也給服務(wù)器一個(gè)遞達(dá)響應(yīng)的時(shí)間。
從使用方式說(shuō)起
不管從0開(kāi)始寫起還是直接下載的Loading插件,都會(huì)抽象為一個(gè)組件,在用到的時(shí)候進(jìn)行加載Loading,或者通過(guò)API手動(dòng)進(jìn)行show或者h(yuǎn)ide
<wait> </wait> ... this.$wait.show() await fetch('http://example.org') this.$wait.hide()
或者通過(guò)Loading狀態(tài)進(jìn)行組件間的切換
<loader v-if="isLoading"> </loader> <Main v-else> </Main>
。要想注冊(cè)成全局狀態(tài),還需要給axios類的網(wǎng)絡(luò)請(qǐng)求包添加攔截器,然后設(shè)置一個(gè)全局Loading狀態(tài),每次有網(wǎng)絡(luò)請(qǐng)求或者根據(jù)已經(jīng)設(shè)置好的URL將Loading狀態(tài)設(shè)置為加載,請(qǐng)求完成后在設(shè)置為完成。
注冊(cè)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 } })
使用時(shí)在每個(gè)組件下獲取出loading狀態(tài),然后判斷什么時(shí)候顯示loading,什么時(shí)候顯示真正的組件。
<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僅能通過(guò)mutations改變. this.$sotre.loading.isLoading = false await axios.get('...') this.$sotre.loading.isLoading = false }, async getMain () { await getMainContent() } ... </script>
在當(dāng)前頁(yè)面下只有一個(gè)需要Loading的狀態(tài)時(shí)使用良好,但如果在同一個(gè)頁(yè)面下有多個(gè)不同的組件都需要Loading,你還需要根據(jù)不同組件進(jìn)行標(biāo)記,好讓已經(jīng)加載完的組件不重復(fù)進(jìn)入Loading狀態(tài)...隨著業(yè)務(wù)不斷增加,重復(fù)進(jìn)行的Loading判斷足以讓人煩躁不已...
整理思路
Loading的核心很簡(jiǎn)單,就是請(qǐng)求服務(wù)器時(shí)需要顯示Loading,請(qǐng)求完了再還原回來(lái),這個(gè)思路實(shí)現(xiàn)起來(lái)并不費(fèi)力,只不過(guò)使用方式上逃不開(kāi)上面的顯式調(diào)用的方式。順著思路來(lái)看,能進(jìn)行Loading設(shè)置的地方有,
- 設(shè)置全局?jǐn)r截,請(qǐng)求開(kāi)始前設(shè)置狀態(tài)為加載。
- 設(shè)置全局?jǐn)r截,請(qǐng)求結(jié)束后設(shè)置狀態(tài)為完成。
- 在觸發(fā)請(qǐng)求的函數(shù)中進(jìn)行攔截,觸發(fā)前設(shè)置為加載,觸發(fā)后設(shè)置為完成。
- 判斷請(qǐng)求后的數(shù)據(jù)是否為非空,如果非空則設(shè)置為完成
最終可以實(shí)現(xiàn)的情況上,進(jìn)行全局?jǐn)r截設(shè)置,然后局部的判斷是最容易想到也是最容易實(shí)現(xiàn)的方案。給每個(gè)觸發(fā)的函數(shù)設(shè)置before
和after
看起來(lái)美好,但實(shí)現(xiàn)起來(lái)簡(jiǎn)直是災(zāi)難,我們并沒(méi)有before
和after
這兩個(gè)函數(shù)鉤子來(lái)告訴我們函數(shù)什么時(shí)候調(diào)用了和調(diào)用完了,自己實(shí)現(xiàn)吧坑很多,不實(shí)現(xiàn)吧又沒(méi)得用只能去原函數(shù)里一個(gè)個(gè)寫上。只判斷數(shù)據(jù)局限性很大,只有一次機(jī)會(huì)。
既然是即插即用的插件,使用起來(lái)就得突出一個(gè)簡(jiǎn)單易用,基本思路上也是使用全局?jǐn)r截,但局部判斷方面與常規(guī)略有不同,使用數(shù)據(jù)綁定(當(dāng)然也可以再次全局響應(yīng)攔截),咱們實(shí)現(xiàn)起來(lái)吧~。
樣式
Loading嘛,必須得有一個(gè)轉(zhuǎn)圈圈才能叫Loading,樣式并不是這個(gè)插件的最主要的,這里直接用CSS實(shí)現(xiàn)一個(gè)容易實(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
把它盤得圓潤(rùn)一些,border
設(shè)置個(gè)進(jìn)度條底座,border-left-color
設(shè)置為進(jìn)度條好了。
綁定數(shù)據(jù)與URL
提供外部使用接口
上面思路中提到,這個(gè)插件是用全局?jǐn)r截與數(shù)據(jù)綁定制作的:
- 暴露一個(gè) source 屬性,從使用的組件中獲取出要綁定的數(shù)據(jù)。
- 暴露一個(gè) 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)控它,每次變化時(shí)都將Loading狀態(tài)設(shè)置為完成即可,urls我們稍后再來(lái)完善它。
設(shè)置請(qǐng)求攔截器
攔截器中需要的操作是將請(qǐng)求時(shí)的每個(gè)URL壓入一個(gè)容器內(nèi),請(qǐng)求完再把它刪掉。
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,但此次插件要突出一個(gè)依賴少,所以Vuex還是不用啦。
直接掛載在Vue上的數(shù)據(jù)不能通過(guò)computed
或者watch
來(lái)監(jiān)控?cái)?shù)據(jù)變化,咱們用Proxy
代理攔截set
方法,每當(dāng)有請(qǐng)求URL壓入時(shí)就做點(diǎn)什么事。Vue.prototype.__loader_checks
用來(lái)存放哪些實(shí)例化出來(lái)的組件訂閱了請(qǐng)求URL時(shí)做加載的事件,這樣每次有URL壓入時(shí),通過(guò)Proxy
來(lái)分發(fā)給訂閱過(guò)得實(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>
每一個(gè)都是一個(gè)嶄新的實(shí)例,所以直接在mounted里訂閱URL事件即可,只要有傳入urls
,就對(duì)__loader_checks
里每一個(gè)訂閱的對(duì)象進(jìn)行發(fā)布,Loader實(shí)例接受到發(fā)布后會(huì)判斷這個(gè)URL是否與自己注冊(cè)的對(duì)應(yīng),對(duì)應(yīng)的話會(huì)將自己的狀態(tài)設(shè)置回加載,URL請(qǐng)求后勢(shì)必會(huì)引起數(shù)據(jù)的更新,這時(shí)我們上面監(jiān)控的source
就會(huì)起作用將加載狀態(tài)設(shè)置回完成。
使用槽來(lái)適配原來(lái)的組件
寫完上面這些你可能有些疑問(wèn),怎么將Loading時(shí)不應(yīng)該顯示的部分隱藏呢?答案是使用槽來(lái)適配,
<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>
還是通過(guò)isLoading
判斷,如果處于加載那顯示轉(zhuǎn)圈圈,否則顯示的是父組件里傳入的槽,
這里寫的要注意,Vue這里有一個(gè)奇怪的BUG,
<div class="loading" v-if="isLoading" :key="'loading'"> </div> <slot v-else> </slot>
在有<slot>
時(shí),如果同級(jí)的標(biāo)簽同時(shí)出現(xiàn)v-if
與CSS選擇器
且樣式是scoped
,那用CSS選擇器
設(shè)置的樣式將會(huì)丟失,<div v-if="isLoading" :key="'loading'">
如果沒(méi)有設(shè)置key
那.loading
的樣式會(huì)丟失,除了設(shè)置key
還可以把它變成嵌套的<div v-if="isLoading"> <div></div> </div>
。
注冊(cè)成插件
Vue中的插件有四種注冊(cè)方式,這里用mixin來(lái)混入到每個(gè)實(shí)例中,方便使用,同時(shí)我們也把上面的axios攔截器也注冊(cè)在這里。
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) ...
任意組件中無(wú)需導(dǎo)入即可使用
<v-loader :source="msg" :urls="['/']"> <div @click="getRoot">{{ msg }}</div> </v-loader>
根據(jù)綁定的數(shù)據(jù)和綁定的URL自動(dòng)進(jìn)行Loading的顯示與隱藏,無(wú)需手動(dòng)設(shè)置isLoading
是不是該隱藏,也不用調(diào)用show
與hide
在請(qǐng)求的方法里打補(bǔ)丁。
其他
上面的通過(guò)綁定數(shù)據(jù)來(lái)判斷是否已經(jīng)響應(yīng),如果請(qǐng)求后的數(shù)據(jù)不會(huì)更新,那你也可以直接在axios的response里做攔截進(jìn)行訂閱發(fā)布模式的響應(yīng)。
最后
咳咳,又到了嚴(yán)(hou)肅(yan)認(rèn)(wu)真(chi)求Star環(huán)節(jié)了,附上完整的項(xiàng)目地址(我不會(huì)告訴你上面的測(cè)試地址里的代碼也很完整的,絕不會(huì)!)。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- vue 全局封裝loading加載教程(全局監(jiān)聽(tīng))
- 基于vue+element實(shí)現(xiàn)全局loading過(guò)程詳解
- vuex+axios+element-ui實(shí)現(xiàn)頁(yè)面請(qǐng)求loading操作示例
- Vue全局loading及錯(cuò)誤提示的思路與實(shí)現(xiàn)
- 詳解Vue項(xiàng)目中出現(xiàn)Loading chunk {n} failed問(wèn)題的解決方法
- vue 使用vant插件做tabs切換和無(wú)限加載功能的實(shí)現(xiàn)
- vue使用video插件vue-video-player詳解
- vue使用video插件vue-video-player的示例
- vue使用screenfull插件實(shí)現(xiàn)全屏功能
- 最全vue的vue-amap使用高德地圖插件畫多邊形范圍的示例代碼
- 寫一個(gè)Vue loading 插件
相關(guān)文章
vue前端框架vueuse的useScroll函數(shù)使用源碼分析
這篇文章主要為大家介紹了vueuse的useScroll函數(shù)源碼分析詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08vue頁(yè)面切換過(guò)渡transition效果
這篇文章主要介紹了vue頁(yè)面切換過(guò)渡transition效果,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-10-10Vue結(jié)合ElementUI實(shí)現(xiàn)數(shù)據(jù)請(qǐng)求和頁(yè)面跳轉(zhuǎn)功能(最新推薦)
我們?cè)赑roflie.vue實(shí)例中,有beforeRouteEnter、beforeRouteLeave兩個(gè)函數(shù)分別是進(jìn)入路由之前和離開(kāi)路由之后,我們可以在這兩個(gè)函數(shù)之內(nèi)進(jìn)行數(shù)據(jù)的請(qǐng)求,下面給大家分享Vue結(jié)合ElementUI實(shí)現(xiàn)數(shù)據(jù)請(qǐng)求和頁(yè)面跳轉(zhuǎn)功能,感興趣的朋友一起看看吧2024-05-05Ant?Design?Vue?Pagination分頁(yè)組件的封裝與使用
這篇文章主要介紹了Ant?Design?Vue?Pagination分頁(yè)組件的封裝與使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-04-04vue項(xiàng)目報(bào)錯(cuò)Uncaught runtime errors的解決方案
使用vue-cli的vue項(xiàng)目,出現(xiàn)編譯錯(cuò)誤或警告時(shí),在瀏覽器中顯示全屏覆蓋,提示報(bào)錯(cuò)Uncaught runtime errors,本文給大家介紹了vue項(xiàng)目報(bào)錯(cuò)Uncaught runtime errors的解決方案,需要的朋友可以參考下2024-01-01