一起写一个即插即用的Vue Loading插件实现


Posted in Javascript onOctober 31, 2019

无论最终要实现怎样的网站,Loading状态都是必不可少的一环,给用户一个过渡喘息的机会也给服务器一个递达响应的时间。

从使用方式说起

不管从0开始写起还是直接下载的Loading插件,都会抽象为一个组件,在用到的时候进行加载Loading,或者通过API手动进行show或者hide

<wait>
</wait>
...
this.$wait.show()
await fetch('http://example.org')
this.$wait.hide()

或者通过Loading状态进行组件间的切换

<loader v-if="isLoading">
</loader>
<Main v-else>
</Main>

。要想注册成全局状态,还需要给axios类的网络请求包添加拦截器,然后设置一个全局Loading状态,每次有网络请求或者根据已经设置好的URL将Loading状态设置为加载,请求完成后在设置为完成。

注册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状态,然后判断什么时候显示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的状态时使用良好,但如果在同一个页面下有多个不同的组件都需要Loading,你还需要根据不同组件进行标记,好让已经加载完的组件不重复进入Loading状态...随着业务不断增加,重复进行的Loading判断足以让人烦躁不已...

整理思路

Loading的核心很简单,就是请求服务器时需要显示Loading,请求完了再还原回来,这个思路实现起来并不费力,只不过使用方式上逃不开上面的显式调用的方式。顺着思路来看,能进行Loading设置的地方有,

  • 设置全局拦截,请求开始前设置状态为加载。
  • 设置全局拦截,请求结束后设置状态为完成。
  • 在触发请求的函数中进行拦截,触发前设置为加载,触发后设置为完成。
  • 判断请求后的数据是否为非空,如果非空则设置为完成

最终可以实现的情况上,进行全局拦截设置,然后局部的判断是最容易想到也是最容易实现的方案。给每个触发的函数设置beforeafter看起来美好,但实现起来简直是灾难,我们并没有beforeafter这两个函数钩子来告诉我们函数什么时候调用了和调用完了,自己实现吧坑很多,不实现吧又没得用只能去原函数里一个个写上。只判断数据局限性很大,只有一次机会。

既然是即插即用的插件,使用起来就得突出一个简单易用,基本思路上也是使用全局拦截,但局部判断方面与常规略有不同,使用数据绑定(当然也可以再次全局响应拦截),咱们实现起来吧~。

样式

Loading嘛,必须得有一个转圈圈才能叫Loading,样式并不是这个插件的最主要的,这里直接用CSS实现一个容易实现又不显得很糙的:

<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设置个进度条底座,border-left-color设置为进度条好了。

演示地址

一起写一个即插即用的Vue Loading插件实现

绑定数据与URL

提供外部使用接口

上面思路中提到,这个插件是用全局拦截与数据绑定制作的:

  • 暴露一个 source 属性,从使用的组件中获取出要绑定的数据。
  • 暴露一个 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>

不用关心source是什么类型的数据,我们只需要监控它,每次变化时都将Loading状态设置为完成即可,urls我们稍后再来完善它。

设置请求拦截器

拦截器中需要的操作是将请求时的每个URL压入一个容器内,请求完再把它删掉。

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实例上,方便我们之后进行调用,当然还可以用Vuex,但此次插件要突出一个依赖少,所以Vuex还是不用啦。

直接挂载在Vue上的数据不能通过computed或者watch来监控数据变化,咱们用Proxy代理拦截set方法,每当有请求URL压入时就做点什么事。Vue.prototype.__loader_checks用来存放哪些实例化出来的组件订阅了请求URL时做加载的事件,这样每次有URL压入时,通过Proxy来分发给订阅过得实例化Loading组件。

一起写一个即插即用的Vue 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里每一个订阅的对象进行发布,Loader实例接受到发布后会判断这个URL是否与自己注册的对应,对应的话会将自己的状态设置回加载,URL请求后势必会引起数据的更新,这时我们上面监控的source就会起作用将加载状态设置回完成。

一起写一个即插即用的Vue Loading插件实现

使用槽来适配原来的组件

写完上面这些你可能有些疑问,怎么将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判断,如果处于加载那显示转圈圈,否则显示的是父组件里传入的槽,
这里写的要注意,Vue这里有一个奇怪的BUG,

<div class="loading" v-if="isLoading" :key="'loading'">
  </div>
  <slot v-else>
  </slot>

在有<slot>时,如果同级的标签同时出现v-ifCSS选择器且样式是scoped,那用CSS选择器设置的样式将会丢失,<div v-if="isLoading" :key="'loading'">如果没有设置key.loading的样式会丢失,除了设置key还可以把它变成嵌套的<div v-if="isLoading"> <div></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>

根据绑定的数据和绑定的URL自动进行Loading的显示与隐藏,无需手动设置isLoading是不是该隐藏,也不用调用showhide在请求的方法里打补丁。

测试地址

其他

上面的通过绑定数据来判断是否已经响应,如果请求后的数据不会更新,那你也可以直接在axios的response里做拦截进行订阅发布模式的响应。

最后

咳咳,又到了严(hou)肃(yan)认(wu)真(chi)求Star环节了,附上完整的项目地址(我不会告诉你上面的测试地址里的代码也很完整的,绝不会!)。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
关于query Javascript CSS Selector engine
Apr 12 Javascript
Javascript 多浏览器兼容总结(实战经验)
Oct 30 Javascript
iframe窗口高度自适应的实现方法
Jan 08 Javascript
jQuery将所有被选中的checkbox某个属性值连接成字符串的方法
Jan 24 Javascript
整理AngularJS框架使用过程当中的一些性能优化要点
Mar 05 Javascript
微信小程序 教程之列表渲染
Oct 18 Javascript
AngularJS实现的简单拖拽功能示例
Jan 02 Javascript
koa2实现登录注册功能的示例代码
Dec 03 Javascript
详解如何实现Element树形控件Tree在懒加载模式下的动态更新
Apr 25 Javascript
微信小程序全局变量的设置、使用、修改过程解析
Sep 24 Javascript
JavaScript canvas动画实现时钟效果
Feb 10 Javascript
js实现烟花特效
Mar 02 Javascript
Vue 使用beforeEach实现登录状态检查功能
Oct 31 #Javascript
vue路由切换之淡入淡出的简单实现
Oct 31 #Javascript
vue-router之实现导航切换过渡动画效果
Oct 31 #Javascript
使用vue-router切换页面时实现设置过渡动画
Oct 31 #Javascript
Vue防止白屏添加首屏动画的实例
Oct 31 #Javascript
vue弹出框组件封装实例代码
Oct 31 #Javascript
使用zrender.js绘制体温单效果
Oct 31 #Javascript
You might like
COM in PHP (winows only)
2006/10/09 PHP
php获得网站访问统计信息类Compete API用法实例
2015/04/02 PHP
Yii2.0框架模型添加/修改/删除数据操作示例
2019/07/18 PHP
PHP实现递归的三种方法
2020/07/04 PHP
JavaScript开发时的五个注意事项
2007/12/08 Javascript
Javascript操作URL函数修改版
2013/11/07 Javascript
js对table的td进行相同内容合并示例详解
2013/12/27 Javascript
js判断手机和pc端选择不同执行事件的方法
2015/01/30 Javascript
纯javascript实现四方向文本无缝滚动效果
2015/06/16 Javascript
js实现新年倒计时效果
2015/12/10 Javascript
基于JavaScript判断浏览器到底是关闭还是刷新(超准确)
2016/02/01 Javascript
javascript瀑布流式图片懒加载实例解析与优化
2016/02/23 Javascript
ES6中的数组扩展方法
2016/08/26 Javascript
前端js弹出框组件使用方法
2020/08/24 Javascript
jQuery ajax的功能实现方法详解
2017/01/06 Javascript
通过npm引用的vue组件使用详解
2017/03/02 Javascript
H5实现中奖记录逐行滚动切换效果
2017/03/13 Javascript
微信小程序 数据绑定及运算的简单实例
2017/09/20 Javascript
vue router+vuex实现首页登录验证判断逻辑
2018/05/17 Javascript
详解Vue.js使用Swiper.js在iOS
2018/09/10 Javascript
Angular+Ionic使用queryParams实现跳转页传值的方法
2020/09/05 Javascript
python、java等哪一门编程语言适合人工智能?
2017/11/13 Python
Python中join函数简单代码示例
2018/01/09 Python
基于pandas数据样本行列选取的方法
2018/04/20 Python
儿童编程python入门
2018/05/08 Python
利用Pyhton中的requests包进行网页访问测试的方法
2018/12/26 Python
使用python将微信image下.dat文件解密为.png的方法
2020/11/30 Python
解决python 输出到csv 出现多空行的情况
2021/03/24 Python
高中生校园生活自我评价
2013/09/19 职场文书
医院后勤自我鉴定
2013/10/13 职场文书
致百米运动员广播稿
2014/01/29 职场文书
大学生求职信范文
2014/05/24 职场文书
《狼牙山五壮士》教学反思
2016/02/17 职场文书
2016年社区国庆节活动总结
2016/04/01 职场文书
你知道哪几种MYSQL的连接查询
2021/06/03 MySQL
Win11电源已接通但未充电怎么办?Win11电源已接通未充电的解决方法
2022/04/05 数码科技