# 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로 변경해서 테스트해보는 것을 권장한다.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.sysout.co.kr/web/develop-page/js/vuejs/vue-cli-3/axios/interceptor.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
