yangkaiyangyi / vue-

12 stars 1 forks source link

关于loading组件实现的思路 #24

Open yangkaiyangyi opened 5 years ago

yangkaiyangyi commented 5 years ago

在项目中,一般是在数据载入前,常常会使用 loding 过渡数据的加载时间。

1.自定义loading组件,一般loading组件作为一个全局的加载状态,建议写在App.vue 中。 这个Loading 组件通过 loading的状态显示隐藏。我们希望在请求某个接口的时候,将 loading 改为true,当接口请求后改为false,这样就能实现loading的过渡效果了。

其中:vuex状态管理器作用于全局,在state中定义存储loading的布尔值,其中loading的布尔值的改变随请求的改变而变,一般是在封装的ajax/axios中请求改变;

app.vue

<template>
  <div id="app">
    <loading v-show="fetchLoading"></loading>
    <router-view></router-view>
  </div>
</template>

<script>
  import { mapGetters } from 'vuex';
  import Loading from './components/common/loading';

  export default {
    name: 'app',
    data() {
      return {
      }
    },
    computed: {
      ...mapGetters([
        'fetchLoading',
      ]),
  },
  components: {
    Loading,
  }
  }
</script>

<style>
  #app{
    width: 100%;
    height: 100%;
  }
</style>

这里的fetchLoading是存在vuex里面的一个变量。在store状态管理器中里需要如下定义: / 此js文件用于存储公用的vuex状态 /

import api from './../../fetch/api'
import * as types from './../types.js'
const state = {
  // 请求数据时加载状态loading
  fetchLoading: false
}
const getters = {
  // 请求数据时加载状态
  fetchLoading: state => state.fetchLoading
}
const actions = {
  // 请求数据时状态loading
  FETCH_LOADING({
    commit
  }, res) {
    commit(types.FETCH_LOADING, res)
  },
}
const mutations = {
  // 请求数据时loading
  [types.FETCH_LOADING] (state, res) {
    state.fetchLoading = res
  }
}

loading组件如下:

<template>
  <div class="loading">
    <img src="./../../assets/main/running.gif" alt="">
  </div>
</template>

<script>
  export default {
    name: 'loading',
    data () {
      return {}
    },
  }
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
  .loading{
    position: fixed;
    top:0;
    left:0;
    z-index:121;
    width: 100%;
    height: 100%;
    background: rgba(0,0,0,0.3);
    display: table-cell;
    vertical-align: middle;
    text-align: center;
  }
  .loading img{
    margin:5rem auto;
  }
</style>

最后在fetch/api.js里封装的axios里写入判断loading事件即可:如下

// axios的请求时间
let axiosDate = new Date()
export function fetch (url, params) {
  return new Promise((resolve, reject) => {
    axios.post(url, params)
      .then(response => {
        // 关闭  loading图片消失
        let oDate = new Date()
        let time = oDate.getTime() - axiosDate.getTime()
        if (time < 500) time = 500
        setTimeout(() => {
          store.dispatch('FETCH_LOADING', false)
        }, time)
        resolve(response.data)
      })
      .catch((error) => {
        // 关闭  loading图片消失
        store.dispatch('FETCH_LOADING', false)
        axiosDate = new Date()
        reject(error)
      })
  })
}
export default {
  // 组件中公共页面请求函数
  commonApi (url, params) {
    if(stringQuery(window.location.href)) {
      store.dispatch('FETCH_LOADING', true);
    }
    axiosDate = new Date();
    return fetch(url, params);
  }
}
--------------------- 

原文

yangkaiyangyi commented 5 years ago

先来张目录结构


1、准备好你想表达的loading界面,这里我就简单的用文字表示,你可以随意发挥。conponents文件下新建Loading.vue

  1. <template>
  2. <div id="loading">
  3. 加载中,请稍后...
  4. </div>
  5. </template>

2、引入App.vue

import Loading from './components/Loading.vue'
  1. components:{
  2. Loading
  3. },
<Loading v-show="showLoading"></Loading>

3,重点来了----- vuex 跟store

Vuex 是专门为 Vue.js 设计的状态管理库,以利用 Vue.js 的细粒度数据响应机制来进行高效的状态更新。store(仓库)是 Vuex 应用的核心。"store" 基本上就是一个容器,它包含着你的应用中大部分的状态(state)。更多专业详情参考https://vuex.vuejs.org/zh-cn/intro.html

首先,在components同级目录下新建文件夹store,内部包含文件store.js, actions.js,mutations.js还有type.js

store.js

  1. import Vue from 'vue'
  2. import Vuex from 'vuex'
  3. import actions from './actions.js'
  4. import mutations from './mutations.js'
  5. Vue.use(Vuex)
  6. export default new Vuex.Store({
  7. modules:{
  8. mutations
  9. },
  10. actions
  11. })


actions.js

  1. import * as types from './type.js'
  2. export default{
  3. showloader:({ commit }) => {
  4. commit( types.SHOWLOADING )
  5. },
  6. hideloader:({ commit }) => {
  7. commit( types.HIDELOADING )
  8. },
  9. }

mutations.js

  1. import { SHOWLOADING,HIDELOADING } from './type.js'
  2. const state = {
  3. showLoading:false
  4. }
  5. const mutations = {
  6. [SHOWLOADING](state){
  7. state.showLoading = true;
  8. },
  9. [HIDELOADING](state){
  10. state.showLoading = false;
  11. }
  12. }
  13. const getters = {
  14. showLoading(state){
  15. return state.showLoading
  16. }
  17. }
  18. export default {
  19. state,mutations,getters
  20. }

type.js

  1. export const SHOWLOADING = 'SHOWLOADING';
  2. export const HIDELOADING = 'HIDELOADING';

直接的关系可以多看看官方文档,,我就用一张图来解释了



刚开始写会觉得麻烦,,但是当你的state需求量增加时,就很方便了,直接按部就班的复制就好。

4、完善一下main.js,配置请求

  1. import Vue from 'vue'
  2. import VueRouter from 'vue-router'
  3. import axios from 'axios'
  4. import App from './App.vue'
  5. import routes from './router.js'
  6. import stores from './store/store.js'
  7. Vue.use(VueRouter)
  8. const router = new VueRouter({
  9.     routes
  10. })
  11. Vue.prototype.$http = axios;
  12. axios.interceptors.request.use(function(config){
  13.     stores.dispatch('showloader')
  14.     return config
  15. },function(err){
  16.     return Promise.reject(err)
  17. });
  18. axios.interceptors.response.use(function(response){
  19.     stores.dispatch('hideloader')
  20.     return response
  21. },function(err){
  22.     return Promise.reject(err)
  23. });
  24. new Vue({
  25.   el: '#app',
  26.   router,
  27.   store:stores,
  28.   render: h => h(App)
  29. })

接下来测试一下,,将focus.vue里获取data方法的文件路径改一下,假装是个错的路径或者加载太慢,run一下后,结果如下



            </div>
yangkaiyangyi commented 5 years ago

也可以通过vuex+vue-router的钩子函数,在跳转成功之前loading为true,在跳转成功之后为false

  1. 修改vue-router,利用router事件钩子,操作控制loader组件的值
    
    import Vue from 'vue'
    import Router from 'vue-router'
    import store from '../store/index'
    import HelloWorld from '@/components/HelloWorld'

Vue.use(Router)

const routes = [ { path: '/', name: 'HelloWorld', component: HelloWorld } ]

// 实例化路由 const router = new Router({ routes })

// 路由跳转前的钩子 router.beforeEach(function (to, from, next) { store.commit('updateLoadingStatus', {isLoading: true}) next() })

// 路由跳转后的钩子 router.afterEach(function (to) { store.commit('updateLoadingStatus', {isLoading: false}) })

yangkaiyangyi commented 5 years ago

在vue项目中自定义组件实现异步请求数据,在获取到数据之前有一个loading的动态图,使界面更友好

在这里定义一个loading组件:

loading.vue:

  1. <template>
  2. <div class="loading-img">
  3. <img src="../../assets/loading.gif" alt="">
  4. </div>
  5. </template>
  6. <script>
  7. export default {
  8. name:'loading'
  9. }
  10. </script>
  11. <style lang="less" scoped>
  12. .loading-img{
  13. position: absolute;
  14. left: 0;
  15. right: 0;
  16. bottom: 0;
  17. top: 0;
  18. width: 100px;
  19. height: 100px;
  20. margin: auto;
  21. img{
  22. width: 100px;
  23. height: 100px;
  24. }
  25. }
  26. </style>

在home页面使用:

  1. <template>
  2. <div>
  3. <h1>home</h1>
  4. <Loading class="loading" v-if="loading"></Loading>
  5. </div>
  6. </template>
  7. <script>
  8. import Loading from '../components/loading.vue'
  9. import {mapState} from 'vuex'
  10. export default {
  11. name: 'home',
  12. data(){
  13. return {
  14. isLoading: false
  15. }
  16. },
  17. beforeCreate(){
  18. document.querySelector('body').setAttribute('style', 'background-color:#fff')
  19. },
  20. beforeDestroy(){
  21. document.querySelector('body').removeAttribute('style')
  22. },
  23. mounted(){
  24. this.$store.state.loading = true
  25. this.$store.dispatch('getNews')
  26. },
  27. components:{Loading},
  28. computed:{
  29. ...mapState([
  30. 'loading'
  31. ])
  32. }
  33. }
  34. </script>
  35. <style lang="less" scoped>
  36. body{
  37. background: red;
  38. position: relative;
  39. }
  40. </style>

store.js:

  1. import Vuex from 'vuex'
  2. import Vue from 'vue'
  3. import axios from 'axios'
  4. Vue.use(Vuex)
  5. export const store = new Vuex.Store({
  6. state:{
  7. newsList: [],
  8. loading: false
  9. },
  10. mutations:{
  11. setNews(state,{newsList}){
  12. state.newsList = newsList
  13. }
  14. },
  15. actions: {
  16. getNews(store){
  17. setTimeout(()=>{
  18. axios.get('/109-35'+'?showapi_appid=73783&showapi_sign=c870408835214a0eacca959aba6f317f').then(res=>{
  19. store.state.loading = false //请求到数据后,loading图消失
  20. })
  21. },2000)
  22. }
  23. }
  24. })

原理:

在vuex中使用一个loading变量来记录动态图的状态,通过开始请求数据和请求到数据这两种状态的改变,来控制动态图的显示与否

yangkaiyangyi commented 5 years ago

vue axios 请求loading 1.在src文件夹里面找到main.js文件

2.在main.js中引入axios,引入mint-ui

    import Axios from 'axios';
    import Mint from 'mint-ui';
    Vue.use(Mint);

3.发起请求,打开loading

    //请求拦截器
    Axios.interceptors.request.use((config) => {
            Mint.Indicator.open({//打开loading
                text: '加载中...',
                spinnerType: 'fading-circle'
            });
            return config;
    }, (err) => {
            return Promise.reject(err)

    })

4.响应回来关闭loading

    //响应拦截器
    Axios.interceptors.response.use((response) => {
            Mint.Indicator.close();//关闭loading
            return response;
    }, (err) => {
            return Promise.reject(err);

    })
yangkaiyangyi commented 5 years ago

Vue基于vuex、axios拦截器实现loading效果及axios的安装配置

准备 利用vue-cli脚手架创建项目 进入项目安装vuex、axios(npm install vuex,npm install axios) axios配置 项目中安装axios模块(npm install axios)完成后,进行以下配置:

main.js

//引入axios import Axios from 'axios'

//修改原型链,全局使用axios,这样之后可在每个组件的methods中调用$axios命令完成数据请求 Vue.prototype.$axios=Axios loading组件 我这里就选择使用iview提供的loading组件,

npm install iview

main.js import iView from 'iview'; import 'iview/dist/styles/iview.css'; Vue.use(iView); 安装引入后,将loading写成一个组件loading.vue

image

Vuex state状态设置控制loading的显隐

store.js(Vuex)

export const store = new Vuex.Store({
    state:{
        isShow:false
    }
})
在state中定义isShow属性,默认false隐藏

v-if="this.$store.state.isShow" 为loading组件添加v-if绑定state中的isShow

组件使用axios请求数据

<button @click="getData">请求数据</button>
methods:{
        getData(){
            this.$axios.get('https://www.apiopen.top/journalismApi')
            .then(res=>{
                console.log(this.mydata)//返回请求的结果
            })
            .catch(err=>{
                console.log(err)
            })
        }
    }

我这里使用一个按钮进行触发事件,利用get请求网上随便找的一个api接口,.then中返回请求的整个结果(不仅仅包括数据)

Axios拦截器配置 main.js

//定义一个请求拦截器

Axios.interceptors.request.use(function(config){
  store.state.isShow=true; //在请求发出之前进行一些操作
  return config
})
//定义一个响应拦截器
Axios.interceptors.response.use(function(config){
  store.state.isShow=false;//在这里对返回的数据进行处理
  return config
})

分别定义一个请求拦截器(请求开始时执行某些操作)、响应拦截器(接受到数据后执行某些操作),之间分别设置拦截时执行的操作,改变state内isShow的布尔值从而控制loading组件在触发请求数据开始时显示loading,返回数据时隐藏loading 特别注意:这里有一个语法坑(我可是来来回回踩了不少次)main.js中调取、操作vuex state中的数据不同于组件中的this.$store.state,而是直接store.state 同上面代码

效果展示 image

yangkaiyangyi commented 5 years ago

axios 实现loading加载提示 思路其实很简单,可自己却想了很久,是太纠结原生的判断了吧TT

使用拦截器就可以了,请求拦截,和响应拦截。其实思考一下。

0 (未初始化): (XMLHttpRequest)对象已经创建,但还没有调用open()方法。 1 (载入):已经调用open() 方法,但尚未发送请求。 2 (载入完成): 请求已经发送完成。 3 (交互):可以接收到部分响应数据。 4 (完成):已经接收到了全部数据,并且连接已经关闭。 // 请求的地方设置成true axios.interceptors.request.use(function () { store.isLoading = true

})

// 响应的地方设置成false axios.interceptors.response.use(function () { store.isLoading = fasle

})