> For the complete documentation index, see [llms.txt](https://docs.sysout.co.kr/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.sysout.co.kr/web/develop-page/js/vuejs/vue-cli-3/axios/interceptor.md).

# interceptor

## Axios Interceptor

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

{% embed url="<https://axios-http.com/kr/docs/interceptors>" %}
axios interceptors reference
{% endembed %}

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

```javascript
// 요청 인터셉터 추가하기
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 형태로 표현하면 다음과 같다.

```javascript
// 요청 인터셉터 추가하기
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에 상태를 저장한다. 초기에는 로딩중이 아닐 것이므로 초기 값으로 <mark style="color:red;">`false`</mark>를 설정한다.

{% code title="/src/store/index.js" %}

```javascript
const store = createStore({
    state: {
        loading:false
    }
});
```

{% endcode %}

이를 확인할 수 있는 <mark style="color:blue;">`getters`</mark> 항목과 로딩 시작과 종료에 대한 설정을 할 수 있도록 <mark style="color:blue;">`mutations`</mark>` ``항목을` 추가한다.

```javascript
getters:{
    isLoading(state){
        return state.loading;
    },
},
mutations:{
    loadingStart(state){
        state.loading = true;
    },
    loadingFinish(state){
        state.loading = false;
    },
},
```

[이동 횟수 측정](/web/develop-page/js/vuejs/vue-cli-3/vuex/move-count.md#undefined)의 코드까지 작성된 전체 파일 코드는 다음과 같다.

<details>

<summary>/src/store/index.js</summary>

```javascript
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;
```

</details>

### Axios interceptor 구현

<mark style="color:blue;">`axios`</mark>를 이용하여 비동기 통신을 진행하기 때문에 시작과 종료 시점에 대한 정보는 axios interceptor를 통해 알 수 있다. 공식 문서의 코드에 <mark style="color:blue;">`Vuex`</mark> 데이터에 대한 처리 코드를 추가한다. <mark style="color:blue;">`Vuex`</mark>가 <mark style="color:red;">`import`</mark> 되어 있어야 한다.

<details>

<summary>/src/ajax/index.js</summary>

```javascript
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;
```

</details>

### vue-spinner 설치

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

{% embed url="<https://www.npmjs.com/package/vue-spinner>" %}

<div align="left"><img src="/files/N3g9FFX4ARFDHiL3rTDs" alt="vue-spinner의 pulse-loader"></div>

#### npm

```bash
npm install vue-spinner
```

### 로딩 화면 구현

모든 화면에서 비동기 요청 발생 시 vue-spinner를 출력할 것이므로 <mark style="color:blue;">`App.vue`</mark> 파일에서 vue-spinner를 출력하는 코드를 작성한다.

{% code title="/src/App.vue" %}

```markup
<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>
```

{% endcode %}

<details>

<summary>전체 코드(App.vue)</summary>

```markup
<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>

```

</details>

### 적용 결과

<div align="left"><img src="/files/QJ6VQ0LG0P4tTBjCCweW" alt=""></div>

### vue-spinner에 vuex 연동

vue-spinner는 통신이 일어날 때에만 표시되어야 하며, 자체적으로 <mark style="color:red;">`loading`</mark>이라는 property를 가지고 있다. <mark style="color:red;">`loading`</mark>에 <mark style="color:blue;">`vuex`</mark>에 저장된 <mark style="color:blue;">`state`</mark> 항목을 연동하여 표시 여부가 <mark style="color:blue;">`vuex`</mark>에 종속되도록 구현한다.

방법은 두 가지가 있다.

1. this.$store 사용
2. mapGetters 사용

### this.$store 사용

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

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

### mapGetters 사용

<mark style="color:blue;">`mapGetters`</mark> 를 이용하면 <mark style="color:blue;">`Vuex`</mark>의 원하는 값을 쉽게 컴포넌트에서 접근할 수 있다.

<mark style="color:blue;">`mapGetters`</mark>를 <mark style="color:red;">import</mark> 하고 컴포넌트의 computed에 다음과 같이 <mark style="color:blue;">`mapGetters`</mark>를 사용한다

```javascript
import { mapGetters } from "vuex";

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

전개연산자 <mark style="color:red;">`...`</mark>를 사용하여 원하는 항목을 computed에 등록할 수 있다. 따라서 <mark style="color:blue;">`pulse-loader`</mark> 태그는 다음과 같이 변한다.

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

### 적용 테스트

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

{% embed url="<https://dummyjson.com/products>" %}
dummy data를 제공하는 사이트
{% endembed %}

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

<div align="left"><img src="/files/HhNUpm0v5AN3Nl4kYloC" alt=""></div>

### 최종 코드

<details>

<summary>/src/App.vue</summary>

```markup
<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>
```

</details>

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