一起寫一個即插即用的Vue Loading插件

無論最終要實現(xiàn)怎樣的網(wǎng)站,Loading狀態(tài)都是必不可少的一環(huán),給用戶一個過渡喘息的機會也給服務器一個遞達響應的時間涡拘。

從使用方式說起

? 不管從0開始寫起還是直接下載的Loading插件唉堪,都會抽象為一個組件,在用到的時候進行加載Loading场刑,或者通過API手動進行show或者hide


? <wait>

? </wait>

? ...

? this.$wait.show()

? await fetch('http://example.org')

? this.$wait.hide()

? 或者通過Loading狀態(tài)進行組件間的切換

? <loader v-if="isLoading">

? </loader>

? <Main v-else>

? </Main>

? 般此。要想注冊成全局狀態(tài),還需要給axios類的網(wǎng)絡請求包添加攔截器牵现,然后設(shè)置一個全局Loading狀態(tài)铐懊,每次有網(wǎng)絡請求或者根據(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 () {

? ? ? ? // 實際情況下State僅能通過mutations改變.

? ? ? ? this.$sotre.loading.isLoading = false

? ? ? ? await axios.get('...')?

? ? ? ? this.$sotre.loading.isLoading = false


? ? },

? ? async getMain () {

? ? ? ? await getMainContent()

? ? }

? ? ...

? ? </script>

? 在當前頁面下只有一個需要Loading的狀態(tài)時使用良好喜喂,但如果在同一個頁面下有多個不同的組件都需要Loading,你還需要根據(jù)不同組件進行標記竿裂,好讓已經(jīng)加載完的組件不重復進入Loading狀態(tài)...隨著業(yè)務不斷增加玉吁,重復進行的Loading判斷足以讓人煩躁不已...



整理思路

Loading的核心很簡單,就是請求服務器時需要顯示Loading腻异,請求完了再還原回來进副,這個思路實現(xiàn)起來并不費力,只不過使用方式上逃不開上面的顯式調(diào)用的方式悔常。順著思路來看影斑,能進行Loading設(shè)置的地方有,

1. 設(shè)置全局攔截机打,請求開始前設(shè)置狀態(tài)為加載矫户。

2. 設(shè)置全局攔截,請求結(jié)束后設(shè)置狀態(tài)為完成残邀。

3. 在觸發(fā)請求的函數(shù)中進行攔截皆辽,觸發(fā)前設(shè)置為加載柑蛇,觸發(fā)后設(shè)置為完成

4. 判斷請求后的數(shù)據(jù)是否為非空驱闷,如果非空則設(shè)置為完成耻台。

最終可以實現(xiàn)的情況上,進行全局攔截設(shè)置空另,然后局部的判斷是最容易想到也是最容易實現(xiàn)的方案盆耽。給每個觸發(fā)的函數(shù)設(shè)置`before`和`after`看起來美好,但實現(xiàn)起來簡直是災難痹换,我們并沒有`before`和`after`這兩個函數(shù)鉤子來告訴我們函數(shù)什么時候調(diào)用了和調(diào)用完了征字,自己實現(xiàn)吧坑很多,不實現(xiàn)吧又沒得用只能去原函數(shù)里一個個寫上娇豫。只判斷數(shù)據(jù)局限性很大匙姜,只有一次機會。

既然是即插即用的插件冯痢,使用起來就得突出一個簡單易用氮昧,基本思路上也是使用全局攔截,但局部判斷方面與常規(guī)略有不同浦楣,使用數(shù)據(jù)綁定(當然也可以再次全局響應攔截)袖肥,咱們實現(xiàn)起來吧~。

樣式

Loading嘛振劳,必須得有一個轉(zhuǎn)圈圈才能叫Loading椎组,樣式并不是這個插件的最主要的,這里直接用CSS實現(xiàn)一個容易實現(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è)置個進度條底座,`border-left-color`設(shè)置為進度條好了弱贼。

演示地址


綁定數(shù)據(jù)與URL

提供外部使用接口

上面思路中提到蒸苇,這個插件是用全局攔截與數(shù)據(jù)綁定制作的:

1. 暴露一個 source 屬性,從使用的組件中獲取出要綁定的數(shù)據(jù)吮旅。

2. 暴露一個 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實例上再层,方便我們之后進行調(diào)用贸铜,當然還可以用Vuex,但此次插件要突出一個依賴少聂受,所以Vuex還是不用啦蒿秦。

直接掛載在Vue上的數(shù)據(jù)不能通過`computed`或者`watch`來監(jiān)控數(shù)據(jù)變化,咱們用`Proxy`代理攔截`set`方法蛋济,每當有請求URL壓入時就做點什么事棍鳖。`Vue.prototype.__loader_checks`用來存放哪些實例化出來的組件**訂閱**了請求URL時做加載的事件,這樣每次有URL壓入時碗旅,通過`Proxy`來分發(fā)給訂閱過得實例化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>

每一個都是一個嶄新的實例,所以直接在mounted里訂閱URL事件即可祟辟,只要有傳入`urls`医瘫,就對`__loader_checks`里每一個訂閱的對象進行發(fā)布薄风,Loader實例接受到發(fā)布后會判斷這個URL是否與自己注冊的對應颗搂,對應的話會將自己的狀態(tài)設(shè)置回加載,URL請求后勢必會引起數(shù)據(jù)的更新斩例,這時我們上面監(jiān)控的`source`就會起作用將加載狀態(tài)設(shè)置回完成吼具。


使用槽來適配原來的組件

寫完上面這些你可能有些疑問僚纷,怎么將Loading時不應該顯示的部分隱藏呢?答案是使用槽來適配拗盒,

<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>`時毅整,如果同級的標簽同時出現(xiàn)`v-if`與`CSS選擇器`且樣式是`scoped`趣兄,那用`CSS選擇器`設(shè)置的樣式將會丟失,`<div class="loading" v-if="isLoading" :key="'loading'">`如果沒有設(shè)置`key`那`.loading`的樣式會丟失悼嫉,除了設(shè)置`key`還可以把它變成嵌套的`<div v-if="isLoading"> <div class="loading"></div> </div>`艇潭。

注冊成插件

Vue中的插件有四種注冊方式,這里用mixin來混入到每個實例中戏蔑,方便使用蹋凝,同時我們也把上面的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)

...

任意組件中無需導入即可使用

<v-loader :source="msg" :urls="['/']">

? <div @click="getRoot">{{ msg }}</div>

</v-loader>

根據(jù)綁定的數(shù)據(jù)和綁定的URL自動進行Loading的顯示與隱藏总棵,無需手動設(shè)置`isLoading`是不是該隱藏鳍寂,也不用調(diào)用`show`與`hide`在請求的方法里打補丁。

測試地址

其他

上面的通過綁定數(shù)據(jù)來判斷是否已經(jīng)響應情龄,如果請求后的數(shù)據(jù)不會更新迄汛,那你也可以直接在axios的response里做攔截進行訂閱發(fā)布模式的響應捍壤。

最后

咳咳,又到了嚴(hou)肅(yan)認(wu)真(chi)求Star環(huán)節(jié)了鞍爱,附上完整的項目地址(我不會告訴你上面的測試地址里的代碼也很完整的鹃觉,絕不會!)睹逃。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末盗扇,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子沉填,更是在濱河造成了極大的恐慌疗隶,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件翼闹,死亡現(xiàn)場離奇詭異斑鼻,居然都是意外死亡,警方通過查閱死者的電腦和手機猎荠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進店門卵沉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人法牲,你說我怎么就攤上這事史汗。” “怎么了拒垃?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵停撞,是天一觀的道長。 經(jīng)常有香客問我悼瓮,道長戈毒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任横堡,我火速辦了婚禮埋市,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘命贴。我一直安慰自己道宅,他們只是感情好,可當我...
    茶點故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布胸蛛。 她就那樣靜靜地躺著污茵,像睡著了一般。 火紅的嫁衣襯著肌膚如雪葬项。 梳的紋絲不亂的頭發(fā)上泞当,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天,我揣著相機與錄音民珍,去河邊找鬼襟士。 笑死盗飒,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的陋桂。 我是一名探鬼主播箩兽,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼章喉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起身坐,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤秸脱,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后部蛇,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體摊唇,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年涯鲁,在試婚紗的時候發(fā)現(xiàn)自己被綠了巷查。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡抹腿,死狀恐怖岛请,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情警绩,我是刑警寧澤崇败,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站肩祥,受9級特大地震影響后室,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜混狠,卻給世界環(huán)境...
    茶點故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一岸霹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧将饺,春花似錦贡避、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至桌肴,卻和暖如春皇筛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背坠七。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工水醋, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留旗笔,地道東北人。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓拄踪,卻偏偏與公主長得像蝇恶,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子惶桐,可洞房花燭夜當晚...
    茶點故事閱讀 45,630評論 2 359

推薦閱讀更多精彩內(nèi)容