Axios에서 로딩을 제어하는 방법

표시 및 로딩 취소는 인터페이스 시 모든 프런트엔드가 고민해야 할 문제라고 할 수 있습니다. 이 기사가 해결하는 데 도움이 되는 것은 axios를 결합하여 표시 및 취소 로딩 논리를 보다 간결하게 처리하는 방법 입니다 .

우선, 우리가 일반적으로 비즈니스를 다룰 때 로딩은 일반적으로 버튼 로딩 , 로컬 로딩 , 글로벌 로딩 의 세 가지 유형으로 나누어집니다 .

버튼 로딩

사실 제가 이 블로그를 쓰고 싶은 이유도 바로 이 버튼 로딩 때문인데요, 우리는 버튼 로딩 비즈니스를 쓸 때 대부분 이렇게 씁니다.

const loading = ref(false)
try {
    loading.value = true
    const data = await axios.post(`/api/data`)
}
finally {
    loading.value = false
}
复制代码

아니면 이렇게 쓰던가

const loading = ref(false)
loading.value = true
axios.post(`/api/data`)
    .then(data => {
        //do something
    })
    .finally(() => {
        loading.value = false
    })
复制代码

우리는 항상 로딩의 시작과 끝 상태를 처리해야 한다는 것을 알 수 있습니다. 그리고 많은 인터페이스가 이런 방식으로 작성되어야 합니다. 너무 번거로운데, 이렇게 하면 될까요?

const loading = ref(false)
const data = await axios.post(`/api/data`,{loading:loading})
复制代码

로딩 상태는 axios에 의해 균일하게 처리됩니다. 이 코드가 훨씬 간단하지 않나요? 처리 방법도 매우 간단합니다.

// 请求拦截器
axios.interceptors.request.use(config = >{
    if (config.loading) {
      config.loading.value = true
    }
})
// 响应拦截器
axios.interceptors.response.use(
    response => {
        if (response.config.loading) {
            res.config.loading.value = false
        }
    },
    error => {
        if (error.config.loading) {
            config.loading.value = false
        }
    }
)
复制代码

axios 인터셉터의 로딩 값만 변경하면 되며, ref 클래스의 값이 전달되어야 한다는 점에 유의하세요. 이 작성 방법은 vue3에만 적용됩니다. vue2가 작동하지 않습니다.

vue2에서는 이렇게 작성하는 것을 생각할 수 있습니다.

<template>
    <a-button loading="loading.value">
        保存
    </a-button>
</template>

<script>
  export default {
    data () {
        return {
            loading: { value: false },
        }
    },
    mounted () {
        const data = await axios.post(`/api/data`,{loading:this.loading})
    },
  }
</script>
//拦截器和vue3写法一样
复制代码

그러나 불행히도 이것은 작동하지 않습니다. 그 이유는 다음과 같습니다

//接口调用
axios.post(接口地址,配置项)
//拦截器
axios.interceptors.request.use(配置项 => {})
复制代码

Axios에서 인터페이스 호출에 의해 전달된 구성 항목과 인터셉터에 의해 반환된 구성 항목은 동일한 메모리 주소가 아닙니다. axios는 딥 카피 처리를 수행합니다. 따라서 전달된 로드 개체와 반환된 로드 개체는 동일한 개체가 아닙니다. 따라서 인터셉터의 수정은 전혀 쓸모가 없습니다.

그런데 왜 vue3이 그렇게 할 수 있나요? ref에 의해 반환된 객체는 RefImpl 클래스의 인스턴스이고 일반 객체가 아니기 때문에 axios는 전체 복사를 수행할 때 그러한 인스턴스 객체를 처리하지 않습니다. 따라서 여기서부터 axios 작성 방법을 변환할 수 있습니다. 코드는 아래와 같이 표시됩니다.

액시오스 코드:

const _axios = axios.create({
  method: `post`,
  baseURL: process.env.VUE_APP_BASE_URL,
})
//注意:拦截器中比vue3多了个loading!!!
// 请求拦截器
_axios.interceptors.request.use(config = >{
    if (config.loading) {
      config.loading.loading.value = true
    }
})
// 响应拦截器
_axios.interceptors.response.use(
    response => {
        if (response.config.loading) {
            res.config.loading.loading.value = false
        }
    },
    error => {
        if (error.config.loading) {
            config.loading.loading.value = false
        }
    }
)

export const post = (url, params, config) => { 
    if (config?.loading) {
        class Loading {
            loading = config.loading
        }
        config.loading = new Loading()
    }
    return _axios.post(url, params, config)
}
复制代码

사용하는 방법:

<template>
    <a-button loading="loading.value">
        保存
    </a-button>
</template>

<script>
  import { post } from '@api/axios'
  export default {
    data () {
        return {
            //这里的loading可以取任意名字。但是里面必须有value
            loading: { value: false },
        }
    },
    mounted () {
        const data = await post(`/api/data`,{loading:this.loading})
    },
  }
</script>
复制代码

구현 원리도 매우 간단하다는 것을 알 수 있습니다. 들어오는 구성의 로딩 객체를 axios의 인스턴스 객체로 바꿀 수 있습니다. 우리가 전달한 객체를 인스턴스 객체에 기록하고, 여기서는 응답성을 달성하기 위해 vue3 작성 방법보다 하나 더 많은 로딩이 있을 것이라고 생각합니다.

부분 로딩

부분 로딩을 추가하는 방법에는 두 가지가 있습니다.

  1. 사용자 정의 지시어를 사용하여 true와 false를 전달합니다. 이러한 단점은 유연성이 충분하지 않다는 점이며, 컴포넌트 내의 요소를 로컬로 추가하기 어렵고 전체 컴포넌트로만 추가할 수 있다는 것입니다. 참과 거짓의 논리를 변경하기 위해 위에서 언급한 버튼 로딩 방법을 사용할 수 있다는 점은 언급할 가치가 있습니다. 여기서는 구체적인 구현 방법을 설명하지 않으며, 필요한 경우 댓글 영역에 메시지를 남길 수 있습니다.

  2. Axios에 포장되어 있습니다. 인터페이스가 호출될 때마다 로드해야 하는 DOM이 전달됩니다. 인터페이스 호출이 완료된 후 dom을 삭제합니다. 수행 방법은 다음과 같습니다.

다음은 vue3 + antdV3 기술 스택 패키지입니다. 여기에서는 로딩 설정 및 삭제 논리를 제거하기 위해 후크가 사용됩니다.

액시오스 코드:

const _axios = axios.create({
  method: `post`,
  baseURL: import.meta.env.VITE_BASE_URL,
})

const { setLoading, deleteLoading } = useAxiosConfig()
// 请求拦截器
_axios.interceptors.request.use(config = >{
    setLoading(config)
})
// 响应拦截器
_axios.interceptors.response.use(
    response => {
        deleteLoading(res.config)
    },
    error => {
        deleteLoading(res.config)
    }
)

export const post = (url, params, config) => { 
    return _axios.post(url, params, config)
}
复制代码

후크 코드

import { createApp } from 'vue'
import QSpin from '@/components/qSpin/QSpin.vue'
import type { RequestConfig, AxiosError } from '@/types/services/http'
export default function () {
  /** 使用WeakMap类型的数据 键名所指向的对象可以被垃圾回收 避免dom对象的键名内存泄漏 */
  const loadingDom = new WeakMap()
  /**
   * 添加局部loading
   * @param config
   */
  const setLoading = (config: RequestConfig) => {
    const loadingTarget = config.dom
    if (loadingTarget === undefined) return
    const loadingDomInfo = loadingDom.get(loadingTarget)
    if (loadingDomInfo) {
      loadingDomInfo.count++
    } else {
      const appExample = createApp(QSpin)
      const loadingExample = appExample.mount(document.createElement(`div`)) as                 InstanceType<typeof QSpin>
      loadingTarget.appendChild(loadingExample.$el)
      loadingExample.show(loadingTarget)
      loadingDom.set(loadingTarget, {
        count: 1, //记录当前dom的loading次数
        appExample,
      })
    }
  }
  /**
   * 删除局部loading
   * @param config
   */
  const deleteLoading = (config: RequestConfig) => {
    const loadingTarget = config.dom
    if (loadingTarget === undefined) return
    const loadingDomInfo = loadingDom.get(loadingTarget)
    if (loadingDomInfo) {
      if (--loadingDomInfo.count === 0) {
        loadingDom.delete(loadingTarget)
        loadingDomInfo.appExample.unmount()
      }
    }
  }
 
  return { setLoading, deleteLoading }
}

复制代码

기본 논리는 매우 간단합니다. 인터페이스 요청이 있을 때만 로딩을 추가하고, 인터페이스 응답이 완료되면 로딩을 삭제하면 됩니다. 하지만 거기에는 문제가 있습니다. 여러 인터페이스가 동시에 요청하거나 인터페이스가 자주 요청하는 경우 모두 동일한 DOM을 커버해야 하므로 우리가 추가하는 로딩에는 서로를 커버하는 동일한 DOM이 많이 있게 됩니다. 따라서 위 코드에서는 현재 어떤 DOM이 로딩되고 있는지 기록하기 위해 loadingDom을 정의하고 있는데, 같은 것이 들어오면 1씩 증가하고 종료 후에는 1씩 감소합니다. 개수가 0이면 로드가 삭제됩니다.

예제 코드 사용:

<template>
  <div>
    <div ref="head_dom">我是头部数据</div>
    <a-card ref="card_dom">我是卡片内容</a-card>
  </div>
</template>

<script setup lang="ts">
  import { post } from '@api/axios'
  import { ref, onMounted } from 'vue'
  const head_dom = ref()
  const card_dom = ref()
  //这边写了两个是为了演示下 直接在html标签上面绑定ref拿到的就是dom。在组件上面拿到的是组件实例要$el一下
  onMounted(async () => {
    const data1 = await post(`/api/head`, { dom: head_dom.value })
    const data2 = await post(`/api/card`, { dom: card_dom.value.$el })
  })
</script>
复制代码

다음은 Hooks 코드 중 QSpin 컴포넌트의 코드에 대한 간략한 설명입니다.

<template>
  <div v-show="visible" class="q-spin">
    <spin tip="加载中" />
  </div>
</template>

<script setup lang="ts">
  import { Spin } from 'ant-design-vue'
  import { ref } from 'vue'

  const visible = ref(false)
  const show = (dom: HTMLElement) => {
    visible.value = true
    dom.style.transform = dom.style.transform || `translate(0)`
  }
  defineExpose({ show })
</script>

<style scoped lang="less">
  .q-spin {
    position: fixed;
    z-index: 999;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    background-color: rgb(0 0 0 / 10%);
  }
</style>
复制代码

다음은 antdv3의 Spin 구성 요소에 대한 간단한 보조 캡슐화입니다. 주요 설명은 들어오는 DOM을 덮어쓰는 로딩 방법입니다.

대부분의 경우 상대 위치 지정과 절대 위치 지정을 조합한 방법을 사용하지만 여기서는 변환 위치 지정과 고정 위치 지정을 조합하여 사용합니다. 우리 프로젝트에 그런 상황이 있을 수도 있으니까요

  <div style="position: relative">
    <div ref="div_dom">
      <div style="position: absolute">我是内容</div>
    </div>
  </div>
复制代码

중간 div에 로딩을 추가하려면 상대 위치와 절대 위치를 조합하여 사용하세요. 그런 다음 가운데 div는 스타일 시트에 position:relative 속성을 추가하므로 코드는 다음과 같습니다.

 <div style="position: relative">
    <div style="position: relative" ref="div_dom">
      <div style="position: absolute">我是内容</div>
    </div>
  </div>
复制代码

분명히 세 번째 레이어 div 위치 지정의 루트 노드가 첫 번째 레이어에서 두 번째 레이어로 변경되어 스타일에 혼란이 발생할 수 있습니다. 따라서 저자는 변환과 고정 위치 지정을 조합하여 사용합니다. 위와 같은 상황이 여전히 발생할 수는 있지만 그 가능성은 크게 줄어들 것입니다.

글로벌 로딩

이것은 매우 간단합니다. 로컬 로딩을 캡슐화했다면 구성 항목의 DOM에 document.body를 직접 전달할 수 있습니다!

おすすめ

転載: blog.csdn.net/weixin_53474595/article/details/130238972