interceptor

Axios Interceptor

axios는 interceptor 기능을 제공하며, 이를 이용하면 통신의 시작, 종료, 오류 발생 시 필요한 작업들을 지정할 수 있다.

공식 문서에 나온 코드는 다음과 같다.

// 요청 인터셉터 추가하기
axios.interceptors.request.use(function (config) {
  // 1. 요청이 전달되기 전에 작업 수행
  return config;
}, function (error) {
  // 2. 요청 오류가 있는 작업 수행
  return Promise.reject(error);
});

// 응답 인터셉터 추가하기
axios.interceptors.response.use(function (response) {
  // 3. 2xx 범위에 있는 상태 코드는 이 함수를 트리거 합니다.
  // 응답 데이터가 있는 작업 수행
  return response;
}, function (error) {
  // 4. 2xx 외의 범위에 있는 상태 코드는 이 함수를 트리거 합니다.
  // 응답 오류가 있는 작업 수행
  return Promise.reject(error);
});

Arrow function 형태로 표현하면 다음과 같다.

// 요청 인터셉터 추가하기
axios.interceptors.request.use(config=>{
  // 1. 요청이 전달되기 전에 작업 수행
  return config;
}, error=>{
  // 2. 요청 오류가 있는 작업 수행
  return Promise.reject(error);
});

// 응답 인터셉터 추가하기
axios.interceptors.response.use(response=>{
  // 3. 2xx 범위에 있는 상태 코드는 이 함수를 트리거 합니다.
  // 응답 데이터가 있는 작업 수행
  return response;
}, error=>{
  // 4. 2xx 외의 범위에 있는 상태 코드는 이 함수를 트리거 합니다.
  // 응답 오류가 있는 작업 수행
  return Promise.reject(error);
});

Loader 추가

로딩 화면을 만들고 싶다면 다음과 같은 작업을 처리해야 한다.

  1. 통신이 시작하면 로딩 화면이 표시될 수 있도록 상태를 설정한다.

  2. 통신이 종료하면 로딩 화면이 사라질 수 있도록 상태를 설정한다.

통신이 종료하는 경우는 총 세 가지이다.

  1. 통신이 성공적으로 완료하는 경우(2xx)

  2. 통신이 완료되었으나 성공 상태가 아닌 경우(3xx, 4xx)

  3. 통신 오류가 발생하는 경우

따라서 위 코드 주석 중 1번 자리에 로딩이 시작했음을 알리는 처리 코드를 작성하고 2, 3, 4번 자리에 로딩이 종료되었음을 알리는 처리 코드를 작성한 뒤 화면과 연결되도록 처리하면 로딩 화면 구현이 완료된다.

Vuex에 상태 저장 설정

로딩 상태를 애플리케이션 전체에서 알 수 있도록 Vuex의 state에 상태를 저장한다. 초기에는 로딩중이 아닐 것이므로 초기 값으로 false를 설정한다.

/src/store/index.js
const store = createStore({
    state: {
        loading:false
    }
});

이를 확인할 수 있는 getters 항목과 로딩 시작과 종료에 대한 설정을 할 수 있도록 mutations 항목을 추가한다.

getters:{
    isLoading(state){
        return state.loading;
    },
},
mutations:{
    loadingStart(state){
        state.loading = true;
    },
    loadingFinish(state){
        state.loading = false;
    },
},
/src/store/index.js
import {createStore} from "vuex";

const store = createStore({
    state:{
        moveCount:0,
        loading:false,//추가
    },
    getters:{
        getMoveCount(state){
            return state.moveCount;
        },
        isLoading(state){//추가
            return state.loading;
        },
    },
    mutations:{
        plusMoveCount(state){
            state.moveCount ++;
        },
        loadingStart(state){//추가
            state.loading = true;
        },
        loadingFinish(state){//추가
            state.loading = false;
        },
    },
    actions:{}
});

export default store;

Axios interceptor 구현

axios를 이용하여 비동기 통신을 진행하기 때문에 시작과 종료 시점에 대한 정보는 axios interceptor를 통해 알 수 있다. 공식 문서의 코드에 Vuex 데이터에 대한 처리 코드를 추가한다. Vueximport 되어 있어야 한다.

/src/ajax/index.js
import axios from "axios";
import store from "@/store";

// 요청 인터셉터 추가
axios.interceptors.request.use(config=>{
    // 1. 요청이 전달되기 전에 작업 수행
    store.commit("loadingStart");
    return config;
  }, error=>{
    // 2. 요청 오류가 있는 작업 수행
    store.commit("loadingFinish");
    return Promise.reject(error);
  });
  
  // 응답 인터셉터 추가
  axios.interceptors.response.use(response=>{
    // 3. 2xx 범위에 있는 상태 코드는 이 함수를 트리거 합니다.
    store.commit("loadingFinish");
    return response;
  }, error=>{
    // 4. 2xx 외의 범위에 있는 상태 코드는 이 함수를 트리거 합니다.
    store.commit("loadingFinish");
    return Promise.reject(error);
  });

export default axios;

vue-spinner 설치

vue-spinner는 Vue Component 형태로 개발된 로딩 화면 도구이다.

npm

npm install vue-spinner

로딩 화면 구현

모든 화면에서 비동기 요청 발생 시 vue-spinner를 출력할 것이므로 App.vue 파일에서 vue-spinner를 출력하는 코드를 작성한다.

/src/App.vue
<template>
    <!-- 생략 -->
    
    <!-- pulse-loader component 화면 배치 -->
    <pulse-loader :loading="true"></pulse-loader>
</template>
<script>
//pulse loader import
import PulseLoaderVue from "vue-spinner/src/PulseLoader.vue";

export default {
  /* 생략 */
  components: {
    //pulse loader 사용 등록
    "pulse-loader":PulseLoaderVue    
  },
}
</script>
<style>
/* 생략 */

/* vue-spinner가 화면 정중앙에 표시되도록 디자인 설 */
.v-spinner {
  position: fixed !important;
  top:50%;
  left:50%;
  transform:translate(-50%, -50%);
}
</style>
전체 코드(App.vue)
<template>
  <img alt="Vue logo" src="@/assets/logo.png">

  <hr>
    <h3>이동 횟수 : {{$store.getters.getMoveCount}}</h3>
  <hr>
  
  <router-link to="/">first screen</router-link>
  &nbsp;&nbsp;&nbsp;
  <router-link to="/second">second screen</router-link>

  <hr>

  <router-view></router-view>

  <pulse-loader :loading="true"></pulse-loader>
</template>

<script>
import PulseLoaderVue from "vue-spinner/src/PulseLoader.vue";

export default {
  name: 'App',
  components: {
    "pulse-loader":PulseLoaderVue    
  },
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
.v-spinner {
  position: fixed !important;
  top:50%;
  left:50%;
  transform:translate(-50%, -50%);
}
</style>

적용 결과

vue-spinner에 vuex 연동

vue-spinner는 통신이 일어날 때에만 표시되어야 하며, 자체적으로 loading이라는 property를 가지고 있다. loadingvuex에 저장된 state 항목을 연동하여 표시 여부가 vuex에 종속되도록 구현한다.

방법은 두 가지가 있다.

  1. this.$store 사용

  2. mapGetters 사용

this.$store 사용

pulse-loader 태그의 loading 항목을 다음과 같이 설정한다.

<pulse-loader :loading="$store.getters.isLoading"></pulse-loader>

mapGetters 사용

mapGetters 를 이용하면 Vuex의 원하는 값을 쉽게 컴포넌트에서 접근할 수 있다.

mapGettersimport 하고 컴포넌트의 computed에 다음과 같이 mapGetters를 사용한다

import { mapGetters } from "vuex";

export default {
    //...
    computed:{
        ...mapGetters(["isLoading"])
    },
    //...
}

전개연산자 ...를 사용하여 원하는 항목을 computed에 등록할 수 있다. 따라서 pulse-loader 태그는 다음과 같이 변한다.

<pulse-loader :loading="isLoading"></pulse-loader>

적용 테스트

Ajax 통신을 수행했을 때 로딩화면이 올바르게 표시되는지 확인해야 하므로 화면이 로딩되는 시점인 mounted 에서 더미 데이터 사이트와 비동기 통신을 실시해서 로더가 정확히 표시되는지 확인한다.

mounted(){
    //global property로 등록한 axios 사용
    this.$http.get("https://dummyjson.com/products")
                .then(resp=>console.log(resp.data));
},

최종 코드

/src/App.vue
<template>
  <img alt="Vue logo" src="@/assets/logo.png">

  <hr>
    <h3>이동 횟수 : {{$store.getters.getMoveCount}}</h3>
  <hr>
  
  <router-link to="/">first screen</router-link>
  &nbsp;&nbsp;&nbsp;
  <router-link to="/second">second screen</router-link>

  <hr>

  <router-view></router-view>

  <!-- <pulse-loader :loading="$store.getters.isLoading"></pulse-loader> -->
  <pulse-loader :loading="isLoading"></pulse-loader>
</template>

<script>
import PulseLoaderVue from "vue-spinner/src/PulseLoader.vue";
import { mapGetters } from "vuex";

export default {
  name: 'App',
  components: {
    "pulse-loader":PulseLoaderVue    
  },
  computed:{
    ...mapGetters(["isLoading"]),
  },
  mounted(){
    //test code
    //this.$http.get("https://dummyjson.com/products")
    //  .then(resp=>console.log(resp.data));
  },
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
.v-spinner {
  position: fixed !important;
  top:50%;
  left:50%;
  transform:translate(-50%, -50%);
}
</style>

vue-spinner에는 다양한 spinner가 존재하므로 다른 spinner로 변경해서 테스트해보는 것을 권장한다.

Last updated