请选择 进入手机版 | 继续访问电脑版

3楼社区

查看: 14|回复: 0

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

[复制链接]

该用户从未签到

2830

主题

2830

帖子

8631

积分

版主

Rank: 7Rank: 7Rank: 7

积分
8631
发表于 2020-11-20 14:45:55 | 显示全部楼层 |阅读模式
写在之前

实现 Loading 思路上并不困难,只不过是根据请求前后进行设置而已,可当要设置的状态越来越多又不能全局统一设置时,就又变得十分繁琐重复。在 Github 和各个社区站里搜 Loading 的插件,每个插件尽管样式上天差地别,但使用起来都没有太大的差别,要么给定 API,手动
  1. show
复制代码
  1. hide
复制代码
,要么封装了一个样式组件,还是需要手动判断什么时候该显示什么时候不该显示。

这个插件所想要解决的问题也是相对容易的自动进行 Loading 状态切换

经过几天的改进,这个版本的插件已经基本符合预期,感谢 @TomVista @shintendo @lllllliu @luoway 在的指教~。

代码中有任何问题 /可以改进的地方都希望能指教一下~(*^▽^*)。
从使用方式说起

不管从 0 开始写起还是直接下载的 Loading 插件,都会抽象为一个组件,在用到的时候进行加载 Loading,或者通过 API 手动进行 show 或者 hide
  1. <wait> </wait> ... this.$wait.show() await fetch(\“http://example.org\“) this.$wait.hide()
复制代码


或者通过 Loading 状态进行组件间的切换
  1. <loader v-if=“isLoading“> </loader> <Main v-else> </Main>
复制代码


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

注册 axios 拦截器:
  1. 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,什么时候显示真正的组件。
  1. <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 设置的地方有,

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


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

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

Loading 嘛,必须得有一个转圈圈才能叫 Loading,样式并不是这个插件的最主要的,相信各位大佬都能写的狂拽酷炫又典雅简约,这里就直接用 CSS 实现一个容易实现又不显得很糙的:
  1. <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 的正方形,使用
  1. border-radius
复制代码
把它盘得圆润一些,
  1. border
复制代码
设置个进度条底座,
  1. border-left-color
复制代码
设置为进度条好了。



  
绑定数据与 URL
提供外部使用接口

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

  • 暴露一个 source 属性,从使用的组件中获取出要绑定的数据。
  • 暴露一个 urls 属性,从使用的组件中获取出要拦截的 URL。
  1. <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 压入一个容器内,请求完再把它删掉。
  1. 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 上的数据不能通过
  1. computed
复制代码
或者
  1. watch
复制代码
来监控数据变化,咱们用
  1. Proxy
复制代码
代理拦截
  1. set
复制代码
方法,每当有请求 URL 压入时就做点什么事。
  1. Vue.prototype.__loader_checks
复制代码
用来存放哪些实例化出来的组件订阅了请求 URL 时做加载的事件,这样每次有 URL 压入时,通过
  1. Proxy
复制代码
来分发给订阅过得实例化 Loading 组件。

  
订阅 URL 事件
  1. <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 事件即可,只要有传入
  1. urls
复制代码
,就对
  1. __loader_checks
复制代码
里每一个订阅的对象进行发布,Loader 实例接受到发布后会判断这个 URL 是否与自己注册的对应,对应的话会将自己的状态设置回加载,URL 请求后势必会引起数据的更新,这时我们上面监控的
  1. source
复制代码
就会起作用将加载状态设置回完成

  
使用槽来适配原来的组件

写完上面这些你可能有些疑问,怎么将 Loading 时不应该显示的部分隐藏呢?答案是使用槽来适配,
  1. <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>
复制代码


还是通过
  1. isLoading
复制代码
判断,如果处于加载那显示转圈圈,否则显示的是父组件里传入的槽, 这里写的要注意,Vue 这里有一个,
  1. <div class=“loading“ v-if=“isLoading“ :key=“\“loading\““> </div> <slot v-else> </slot>
复制代码


在有
  1. <slot>
复制代码
时,如果同级的标签同时出现
  1. v-if
复制代码
  1. CSS 选择器
复制代码
且样式是
  1. scoped
复制代码
,那用
  1. CSS 选择器
复制代码
设置的样式将会丢失,
  1. <div class=“loading“ v-if=“isLoading“ :key=“\“loading\““>
复制代码
如果没有设置
  1. key
复制代码
  1. .loading
复制代码
的样式会丢失,除了设置
  1. key
复制代码
还可以把它变成嵌套的
  1. <div v-if=“isLoading“> <div class=“loading“></div> </div>
复制代码

注册成插件

Vue 中的插件有,这里用mixin来混入到每个实例中,方便使用,同时我们也把上面的 axios 拦截器也注册在这里。
  1. 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) } }) } }
复制代码

使用

在入口文件中使用插件
  1. import Loader from \“./plugins/loader/index.js\“ ... Vue.use(Loader) ...
复制代码


任意组件中无需导入即可使用
  1. <v-loader :source=“msg“ :urls=“[\“/\“]“> <div @click=“getRoot“>{{ msg }}</div> </v-loader>
复制代码


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


其他

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

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

实现 Loading 思路上并不困难,只不过是根据请求前后进行设置而已,可当要设置的状态越来越多又不能全局统一设置时,就又变得十分繁琐重复。在 Github 和各个社区站里搜 Loading 的插件,每个插件尽管样式上天差地别,但使用起来都没有太大的差别,要么给定 API,手动
  1. show
复制代码
  1. hide
复制代码
,要么封装了一个样式组件,还是需要手动判断什么时候该显示什么时候不该显示。

这个插件所想要解决的问题也是相对容易的自动进行 Loading 状态切换

经过几天的改进,这个版本的插件已经基本符合预期,感谢 @TomVista @shintendo @lllllliu @luoway 在的指教~。

代码中有任何问题 /可以改进的地方都希望能指教一下~(*^▽^*)。
从使用方式说起

不管从 0 开始写起还是直接下载的 Loading 插件,都会抽象为一个组件,在用到的时候进行加载 Loading,或者通过 API 手动进行 show 或者 hide
  1. <wait> </wait> ... this.$wait.show() await fetch(\“http://example.org\“) this.$wait.hide()
复制代码


或者通过 Loading 状态进行组件间的切换
  1. <loader v-if=“isLoading“> </loader> <Main v-else> </Main>
复制代码


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

注册 axios 拦截器:
  1. 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,什么时候显示真正的组件。
  1. <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 设置的地方有,

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


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

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

Loading 嘛,必须得有一个转圈圈才能叫 Loading,样式并不是这个插件的最主要的,相信各位大佬都能写的狂拽酷炫又典雅简约,这里就直接用 CSS 实现一个容易实现又不显得很糙的:
  1. <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 的正方形,使用
  1. border-radius
复制代码
把它盘得圆润一些,
  1. border
复制代码
设置个进度条底座,
  1. border-left-color
复制代码
设置为进度条好了。



  
绑定数据与 URL
提供外部使用接口

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

  • 暴露一个 source 属性,从使用的组件中获取出要绑定的数据。
  • 暴露一个 urls 属性,从使用的组件中获取出要拦截的 URL。
  1. <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 压入一个容器内,请求完再把它删掉。
  1. 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 上的数据不能通过
  1. computed
复制代码
或者
  1. watch
复制代码
来监控数据变化,咱们用
  1. Proxy
复制代码
代理拦截
  1. set
复制代码
方法,每当有请求 URL 压入时就做点什么事。
  1. Vue.prototype.__loader_checks
复制代码
用来存放哪些实例化出来的组件订阅了请求 URL 时做加载的事件,这样每次有 URL 压入时,通过
  1. Proxy
复制代码
来分发给订阅过得实例化 Loading 组件。

  
订阅 URL 事件
  1. <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 事件即可,只要有传入
  1. urls
复制代码
,就对
  1. __loader_checks
复制代码
里每一个订阅的对象进行发布,Loader 实例接受到发布后会判断这个 URL 是否与自己注册的对应,对应的话会将自己的状态设置回加载,URL 请求后势必会引起数据的更新,这时我们上面监控的
  1. source
复制代码
就会起作用将加载状态设置回完成

  
使用槽来适配原来的组件

写完上面这些你可能有些疑问,怎么将 Loading 时不应该显示的部分隐藏呢?答案是使用槽来适配,
  1. <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>
复制代码


还是通过
  1. isLoading
复制代码
判断,如果处于加载那显示转圈圈,否则显示的是父组件里传入的槽, 这里写的要注意,Vue 这里有一个,
  1. <div class=“loading“ v-if=“isLoading“ :key=“\“loading\““> </div> <slot v-else> </slot>
复制代码


在有
  1. <slot>
复制代码
时,如果同级的标签同时出现
  1. v-if
复制代码
  1. CSS 选择器
复制代码
且样式是
  1. scoped
复制代码
,那用
  1. CSS 选择器
复制代码
设置的样式将会丢失,
  1. <div class=“loading“ v-if=“isLoading“ :key=“\“loading\““>
复制代码
如果没有设置
  1. key
复制代码
  1. .loading
复制代码
的样式会丢失,除了设置
  1. key
复制代码
还可以把它变成嵌套的
  1. <div v-if=“isLoading“> <div class=“loading“></div> </div>
复制代码

注册成插件

Vue 中的插件有,这里用mixin来混入到每个实例中,方便使用,同时我们也把上面的 axios 拦截器也注册在这里。
  1. 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) } }) } }
复制代码

使用

在入口文件中使用插件
  1. import Loader from \“./plugins/loader/index.js\“ ... Vue.use(Loader) ...
复制代码


任意组件中无需导入即可使用
  1. <v-loader :source=“msg“ :urls=“[\“/\“]“> <div @click=“getRoot“>{{ msg }}</div> </v-loader>
复制代码


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


其他

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

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

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|

快速回复 返回顶部 返回列表