No matter what kind of web site is eventually implemented, Loading state is an essential part of the process to give the user a chance to breathe and the server a time to deliver the response.

Let’s start with the way we use it

Loading plugins, whether written from 0 or downloaded directly, are abstracted into a component and loaded when used, or manually show or hide through the API

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

Alternatively, switch between components using the Loading state

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

. To register the global state, add an interceptor to the Axios class network request packet and set the Loading state to Loading every time a network request is made or the URL has been set, and set the Loading state to Loading after the request is completed.

Register the AXIOS interceptor:

  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}})Copy the code

Loading state is obtained for each component and then determines when loading is displayed and when loading is displayed for the actual component.

<template>
  <div>
    <loader v-if="isLoading">
    </loader>
    <Main v-else>
    </Main>
  </div>
 </template>
 <script>
 ...
 components: {
     loader
 },
 computed: {
     isLoading: this.$store.loading.isLoading
 },
 async getMainContent() {// In reality, State can only be changed by mutations. This.$sotre.loading.isLoading = false
     await axios.get('... ')  
     this.$sotre.loading.isLoading = false
     
 },
 async getMain () {
     await getMainContent()
 }
 ...
 </script>
Copy the code

This is used when there is only one Loading state on the current page. However, if there are multiple components in Loading state on the same page, you need to mark each component so that the Loading state is not repeated… With the increase of business, repeated Loading judgment is enough to make people restless…

To compose

The core of Loading is very simple, is to request the server to display Loading, the request is finished and then back, this idea is not difficult to implement, but the use of the way can not escape the above explicit call mode. So the way to think about this is that the Loading setting is,

  1. Set global intercept and set the state to load before the request starts.
  2. Set global intercept and set the status to Complete when the request is completed.
  3. Interception is in the function that triggers the request, set to load before firing and complete after firing.
  4. Determines whether the data after the request is non-empty, and if not, sets it to complete

In cases where it is ultimately possible to do so, setting up a global interception and then judging it locally is the easiest to think of and implement. It’s nice to set before and after for each function that’s triggered, but it’s a disaster to implement. We don’t have before and after hooks to tell us when a function has been called and when it’s finished. Judging only the data is very limited, only one chance.

Since it is a plug and play plug-in, it should be easy to use. The basic idea is to use global interception, but the local judgment aspect is slightly different from the normal, using data binding (of course, you can also use global response interception again). Let’s implement it.

style

Loading must be done with a loop. The style is not the most important part of this plugin. Here is a straightforward CSS implementation that is easy to implement and not too rough:

<template>
   <div class="loading"> </div> </template> ... <style scoped> .loading { width: 50px; height: 50px; Border: 4 px 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>Copy the code

Set the size of the square to 50px, use the border-RADIUS to make it round. set the border to the base of the progress bar and the border-left-color to the progress bar.

Demo address

Bind data to a URL

Provides interfaces for external use

As mentioned above, this plugin is made with global interception and data binding:

  1. Expose a source property to get the data to be bound from the component being used.
  2. Expose a URL property to retrieve the URL to intercept from the component being used.
<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>
Copy the code

Don’t care what type of data the source is, we just need to monitor it, set Loading state to done every time it changes, urls and we’ll fix that later.

Set the request interceptor

The action required in the interceptor is to push each URL requested into a container and then delete it after the request.

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
})
Copy the code

Mount it on a Vue instance so we can call it later, and of course Vuex can be used, but this time the plugin will highlight one less dependency, so Vuex will not be used.

Data mounted directly on Vue cannot be monitored on computed or watch. We use Proxy to intercept the set method and do something whenever a request URL is pressed. Ue. Prototype. __loader_Checks is used to store the Loading event of the subscribed URL. Each time a URL is loaded, it is distributed to the Loading component through the Proxy.

Subscribe to URL events

<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>
Copy the code

Each of these urls is a new instance, so you can subscribe to the URL event in mounted. Whenever there is an incoming URL, it will publish every subscribed object in __loader_Checks. The Loader instance will determine if the URL corresponds to the one it registered. If so, it will set its own state back to load, and the URL request will inevitably cause the data update, at this time, the source we monitor above will take effect and set the loading state back to complete.

Use the slot to fit the original component

After writing this, you may be wondering how to hide parts that should not be displayed during Loading. The answer is to use slots to fit,

<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>
Copy the code

If it isLoading then it will be shown in a loop, otherwise it will be shown in a slot passed in by the parent component.

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

If both the v-if and the CSS selectors are scoped, the CSS selectors will be lost.

Registering as a plug-in

There are four ways to register plug-ins in Vue. Mixins are used here to mixin each instance for ease of use, and we also register the axios interceptor above.

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)            
            }
        })        
    } 
}

Copy the code

use

Use the plug-in in the entry file

import Loader from './plugins/loader/index.js'. Vue.use(Loader) ...Copy the code

It can be used in any component without import

<v-loader :source="msg" :urls="['/']">
  <div @click="getRoot">{{ msg }}</div>
</v-loader>
Copy the code

Loading is automatically displayed and hidden according to the bound data and bound URL. You do not need to manually set whether isLoading is hidden or not. You do not need to call show and hide to patch the requested method.

Address of the test

other

If the data is not updated after the request, you can also intercept and subscribe to the publisit-mode response directly in Axios’s Response.

The last

Hou, it’s time to ask for Star again, attached is the complete project address (I won’t tell you that the code in the above test address is also complete, never!) .