Vue 3에서 소개된 Composition API는 컴포넌트 로직을 보다 간결하게 작성하고 재사용하기 위한 강력한 도구다. 이 글에서는 실제로 사용된 코드를 통해 Composition API의 핵심 원리를 살펴보겠다.

핵심 개념
1. ref 함수

import { ref } from 'vue';

const myVar = ref(0);

ref 함수는 반응적인(reactive) 데이터를 생성한다. 이렇게 생성된 변수는 변경되면 자동으로 화면을 업데이트한다. myVar.value로 현재 값을 얻을 수 있다.

 

 

 

2. reactive 함수, computed 함수, watch 함

import { reactive } from 'vue';

const myObject = reactive({ prop: 'value' });

import { computed } from 'vue';

const double = computed(() => myVar.value * 2);


import { watch } from 'vue';

watch(() => myVar.value, (newValue, oldValue) => {
  console.log(`myVar changed from ${oldValue} to ${newValue}`);
});

 

reactive 함수는 객체를 반응적으로 만들어준다. 객체의 속성이 변경되면 화면이 자동으로 갱신된다. 
computed 함수는 계산된 값을 만들어준다. 종속성을 추적하여 해당 값이 변경될 때까지 다시 계산하지 않는다.

watch 함수는 데이터의 변화를 감지하고 그에 따른 동작을 수행할 수 있도록 한다.

옵션 api 에서는 구조적으로 있었으나 import 선언으로 쓸 수 있게 변경이 되었다.

3. 비동기 작업과 onMounted

const adminCategoriesListInit = async () => {
  loadCategories();
};

onMounted(adminCategoriesListInit);

 

onMounted는 컴포넌트가 마운트된 후 실행되는 라이프사이클 훅입니다. 비동기 작업을 수행하는 함수를 호출하여 초기 데이터를 가져올 수 있다. 모든 라이프 사이클 에는 on 이 붙는다.

 

4. setup 함수

setup() {
  // 초기 설정 및 로직 작성
  return { myVar, double };
}

 

setup 함수는 컴포넌트의 초기 설정을 담당하며, 여기서 리액티브 데이터, 메소드, 훅 등을 반환한다.

 

 

Vue 3 Composition API는 코드를 보다 모듈화하고 가독성 있게 작성할 수 있는 강력한 기능을 제공한다. 이를 통해 개발자는 더 효율적으로 코드를 작성하고 유지보수할 수 있다. 기존 옵션 api 는 다르게 구조는 파악이 힘들지만 더 간결한 코드로 작성이 가능하다.

 

 

node express 에 대한 개념 설명과 개념을 정리 해보겠다.

 

1. 미들웨어 스택

express 서버 코드의 핵심은 클라이언트의 각 요청을 처리하는 라우팅 로직을 구현하는것이고, 라우팅 로직을 구현하는 것은 곧 미들웨어 스택을 구출을 의미한다. 미들웨어 스택은 클라이언트의 요청을 처리하는 함수들이 설정된 순서대로 저장되어있는 구조를 의미한다. 이는 미들웨어, 라우터 핸들러, 에러 핸들러로 구성되어있다. 라우터라는 단위로 묶을 수 있다.

 

1. 애플리케이션 레벨 미들웨어

애플리케이션 레벨이란 아래서 살펴볼 express()로 생성할 수 있는 app 객체의 app.use()나 app.METHOD()(ex. app.get, app.post) 함수를 이용해 미들웨어를 app 인스턴스에 바인딩하는 미들웨어이다. 마운트 경로가 없는 미들웨어 함수는 앱이 요청을 수신할 때마다 실행하게 된다.

 

app.get('/pages/', (req, res, next) => {
  console.log('Time : ', Date.now());
  next();
});

app.get((req, res, next) => {
  console.log('Not Found');
});

 

하나의 경로에 app 메서드 여러개가 묶일 수 있는데 다음예시를 주의깊게 보자

 

app.get('/pages/:id', (req, res, next) => {
  if (req.params.id == 0) next('route');
  else next();
}, (req, res, next) => {
  res.send('regular');
}
});

app.get('/pages/:id', (req, res, next) => {
  res.send('special');
}

 

위에 경우는 2개묶인 app 메서드가 위에 있으므로 special 보다 먼저 실행된다. next() 는 지금 라우터 미들웨어 스택을 벗어난 다음 라우터로 제어가 넘어가게 하는것이다. 라우터 미들워ㅔ어 스택이란 하나의 app.use()나 app.메서드 에 묵인 라우팅 미들웨어들을 말한다.

여기서 그냥 next()는 regular로 넘어가게 된다. id가 0이 아니라서 regular로 넘어간다면, 거기서 next()를 호출하지 않았으므로 special은 호출되지 않는다. 

next() 안에 인자가 들어가는 경우는 아마 next('route')와 next(err)뿐일 것 같다. next 안에 뭔가 인자가 들어가면 express 앱은 그것을 오류 발생이라고 보고 오류 처리와 관련없는 다른 모든 미들웨어를 다 건너뛰고 오류 처리(error handling) 라우터로 넘어간다. 단 하나 'route'만 빼고 말이다. 'route'는 현재 메소드를 벗어나 path에 해당하는 다음 라우터로 제어가 넘어가는 것이다.

 

2. 라우터 레벨 미들웨어

라우터 레벨은 express.Router()로 생성할 수 있는 router 인스턴스에 미들웨어가 바인딩되는 것이다. 그것 외에는 애플리케이션 레벨 미들웨어가 차이가 없다. router.use()나 router.METHOD() 함수를 이용해 로드할 수 있다.

express.Router() 로 router 객체를 생성할 수 있는데, 미들웨어와 HTTP 메소드 라우트를 router 객체에 붙일 수 있다.

 

//app.js
const express = require('express');
const app = express();
const pageRouter = ('./routes/pages');
app.use('/pages', pageRouter);
//pages.js
const express = require('express');
const router = express.Router();

router.get('/pages/:id', (req, res, next) => {
  //pages id가 0이면 'regular'가 아닌 'special'로 넘어감
  if (req.params.id == 0) next('route');
  //pages id가 0이 아니라면 'regular'로 넘어감
  else next();
}, (req, res, next) => {
  res.send('regular');
}
});

//pages id가 0일 때 넘어올 미들웨어
router.get('/pages/:id', (req, res, next) => {
  res.send('special');
}

module.exports = router;

 

3. 오류 처리 미들웨어

오류 처리 미들웨어는 (err, req, res, next)를 인자로 받는 것이다. 항상 4개의 매개변수가 필요하다. 이게 오류 처리 미들웨어의 시그니처다. err.stack으로 에러 메시지를 볼 수 있다.

 

//오류의 종류에 상관없이 모든 오류를 잡는 미들웨어
app.get((err, req, res, next) => {
  console.log(err.stack);
  res.status(500).send('Something broke!');
});

주의할 점은 오류 처리 미들웨어는 app.use() 및 라우트 호출을 정의한 뒤 거의 코드의 맨 끝에 정의해야 한다는 점이다. 

위의 경우 모든 오류를 잡는 미들웨어 하나만 만들었는데, 에러마다 다른 오류 처리 미들웨어 함수를 정의할 수도 있다. 이 경우 catch-all 에러 핸들러는 그 오류 처리 미들웨어들 중에서도 가장 아래 있어야 한다.

오류 처리 미들웨어는 다음과 같이 부를 수 있다.

 

4. 기본 제공 미들웨어

기본 제공은 정적 리소스를 제공할 루트 디렉토리를 정하는 express.static 같은 것이 있다. 현재 문서에는 빌트인 미들웨어는 express.static 뿐이라고 되어 있다. 정적 파일을 전달해주는데, 여기서는 /public 디렉토리가 정적 파일들이 모여 있는 루트 디렉토리가 된다. 아래서 더 자세히 설명하겠다.

app.use(express.static(__dirname + '/public'));

 

 

5. 써드파티 미들웨어

마지막으로 써드파티 미들웨어는 npm 에서 설치한 helmet이나 cookie-parser 같은 모듈들이 해당이 된다. 쉽게 말해 express 자체적으로 제공하지 않고 따로 설치해야 하는 것들은 다 써드파티라고 보면 된다.

npm i cookie-parser
const express = require('express');
const app = express();
const cookieParser = require('cookie-parser');

app.use(cookieParser());

이 때 cookieParser()를 하면 미들웨어를 반환한다. 

Vue 자바스크립트 기반의 프레임워크

Vue.js(뷰 제이에스)는 자바스크립트 기반의 프론트엔드 프레임워크로, 사용자 인터페이스를 구축하기 위한 라이브러리입니다. 다른 프레임워크와 비교했을 때 가볍고 진입 장벽이 낮으며, 쉬운 문법과 뛰어난 성능으로 개발자들 사이에서 인기를 얻고 있다.

 

주요 특징:

  1. 바인딩 (Data Binding):
    • Vue.js는 데이터와 화면 요소 간의 양방향 데이터 바인딩을 지원합니다. 이는 모델과 뷰 간의 동기화를 쉽게 유지할 수 있도록 도와준다
  2. 컴포넌트 기반 구조:
    • Vue.js는 컴포넌트 기반 아키텍처를 채택하고 있습니다. 이는 애플리케이션을 작은 단위로 나누어 각각을 재사용 가능한 컴포넌트로 구성함으로써 유지보수성을 높이고 코드의 가독성을 향상시긴다.
  3. 가상 돔 (Virtual DOM):
    • Vue.js는 가상 돔을 활용하여 실제 DOM 조작을 최소화하고 성능을 향상시킵니다. 변경된 부분만을 실제 DOM에 적용하여 불필요한 렌더링을 방지한다.
  4. 디렉티브 (Directives):
    • Vue 디렉티브는 v- 접두어를 가진 특수 속성으로, HTML에서 사용자 정의 속성처럼 사용됩니다. 예를 들어, v-if, v-for, v-bind, v-on 등이 있다.
  5. 이벤트 핸들링 및 메소드:
    • Vue에서는 v-on 디렉티브를 통해 이벤트 핸들링을 지원하며, 메소드를 정의하여 이벤트에 대한 로직을 처리할 수 있다.
  6. 라우팅 (Vue Router):
    • Vue Router를 이용하여 SPA(Single Page Application)를 쉽게 구현할 수 있습니다. 라우터를 사용하면 페이지 간의 전환과 브라우저 히스토리를 관리할 수 있다.
  7. 상태 관리 (Vuex):
    • 대규모 애플리케이션에서 상태 관리를 위한 Vuex를 제공합니다. 중앙 집중식 상태 관리 패턴을 사용하여 애플리케이션의 상태를 효과적으로 관리한다.
  8. 라이프사이클 훅:
    • Vue 인스턴스의 라이프사이클에 특정 로직을 실행할 수 있는 훅을 제공하여 애플리케이션의 특정 시점에 작업을 수행할 수 있다.

최근 회사에서는 react 로 작업중에서 느낀점이지만 vue 는 좀더 가독성이 높고 간결한 코드를 작성하기에 최적화 되어있다고 생각이 들었다. 프레임워크로 리액트는 라이브러리를 추가하여 리덕스 zustand 를 사용하여 상태관리를 하는 방면 vue 는 따로 store 관리를 라이브러리 설치를 하지 않고도 진행 할 수 있는 점이 가장 큰 장점으로 다가왔다.

 

라이프 사이클또한 

공식문서 캡처 내용

위에처럼 여러가지로 생명주기 훅으로 사용한다. 리액트에서는 useEffect 로 동기화 작업을 한다면 vue 는 라이프 사이클로 전반적인 동기화 작업을 진행시킨다. 의존성을 주입해서 감시하는 useEffect 와 차이점으로는 감시자는 watch 라는 감시자가 있는데 리액트와 다른 강점을 가지고 있다. 하지만 watch 는 주의해야할점이 있다. 명령형 프로그래밍 방식이므로

computed 속성을 사용해야하는경우는 구분하여 사용해야한다. 선언형 프로그래밍 방식인 컴퓨티트는 computed 속성은 종속 대상을 따라 저장하며 캐싱처리를 하기떄문에 구분하여 사용 한다.

 

vue3

vue3가 도입되면서 달라진 점이 있다.

2022년 1월에 새로 도입된 vue3는

1. 성능 향상

 

[가상 DOM 최적화]

 

기존 Vue 렌더링의 가상 DOM 설계 HTML 기반 템플릿을 제공하고 이를 가상 DOM Tree로 반환한 후 실제 DOM의 어떤 영역이 업데이트되어야 하는지 재귀적으로 탐색하는 방식이었다. 그렇기에, 매 변경을 파악하기 위해 모든 트리를 확인하는 비효율성이 존재했다.

  • 템플릿 구문에서 정적 요소와 동적 요소를 구분, 트리를 순환할 떄 동적 요소만 순환해서 탐색의 최적화를 반영했다.
  • 렌더링 관련 객체(템플릿 내 정적 요소, 서브 트리, 데이터 객체) 등을 컴파일러가 탐지해 Renderer 함수 밖으로 호이스팅시켜 객체의 복수 생성을 방지한다.
  • 컴파일러가 템플릿 내 동적 바인딩 요소에 플래그를 생성한다. 이를 통해 렌더링 속도를 향상시켰다.

 

[트리쉐이킹 강화]

 

트리 쉐이킹(Tree Shaking)이란 나무를 흔들어 잎을 떨어트리듯 모듈을 번들링하는 과정에서 사용하지 않는 코드를 제거하여 사이즈를 줄이는 최적화 방안을 의미한다.

Vue3는 컴파일러가 실제 사용하는 코드만 import하며, v-model과 같은 양방향 바인딩에서 트리 쉐이킹을 적용하여 번들 크기를 절반 이상으로 줄인다.

 

2. Composition API

Vue3의 가장 큰 특징이라고 할 수 있는 Composition API가 등장함에 따라, 함수형 프로그래밍 기반의 코드 템플릿의 변화가 일어났다.

여기서는 대표적으로 바뀐 부분들만 짚고, 자세한 내용은 별도로 정리한 포스팅을 참고해주기를 바란다!

  • setup() 메서드 : 기존 컴포넌트 옵션들을 setup() 메서드 내에 선언 및 반환한다. 데이터에 반응형을 부여하는 ref() 및 reactive(), 기존의 computed(), watch() 모두 API 메서드들로 대체되었다.
  • props : this 바인딩을 하던 방식에서, setup() 의 첫번째 인자로 받아 내부에서 활용할 수 있다.
  • emit : this 바인딩을 하던 방식에서, setup() 의 두번째 인자인 context에 포함되어있다.
  • Lifecycle Hooks : beforeCreate, created 가 setup() 으로 대체된다. 또, hooks 앞에 on들이 붙었으며, destroy는 unmount로 변경되었다.

이러한 컴포지션 API를 통해 코드의 가독성 향상, 재사용성의 개선, Typescript 타입 추론 등의 지원이 가능해졌다.

 

3. Fragment

Vue2 에서는 <template> 내에 단일 태그로 랩핑을 필수적으로 해야 했다. (Vue 인스턴스를 단일 DOM 요소로 바인딩했어야함)

하지만, Vue3 는 <Fragment> 태그를 지원하며, 이를 통해 다중루트 노드(multiple root node)를 작성할 수 있게 되었다. (Fragment는 DOM 트리에 그려지지 않음)

 

<!-- Vue2 -->
<template>
  <div>
    <header>...</header>
    <main>...</main>
    <footer>...</footer>
  </div>
</template>


<!-- Vue3 -->
<template>
  <header>...</header>
  <main v-bind="$attrs">...</main>
  <footer>...</footer>
</template>

 

4. Teleport

React의 Portals(포털)과 유사한 기능으로, 모달이나 알림 등과 같이 특정 컴포넌트가 부모에 상속되어 있으면서도 렌더링되는 위치는 제 2의 루트(영역)인게 유리한 경우 Teleport 기능이 유용하다.

이를 통해, 기존에 CSS로 조정하거나 인위적으로 엘리먼트를 주입하던 방식에서, <teleport> 태그 내부의 HTML 요소를 특정 태그로 옮겨 렌더링할 수 있게 되었다.

 

// Modal.vue
<teleport to="#deleteModal">
  <Modal v-show="showModal" @delete="deleteTodo" @close="closeModal"></Modal>
</teleport>

 

위 예시와 같이, 다른 곳에 렌더링할 요소를 <teleport> 태그로 감싸고, to 속성에 렌더링할 위치의 id(혹은 class)명을 설정한다.

 

// index.html
<body>
  <div id="app"></div>
  <div id="deleteModal"></div>
</body>

 

다음과 같이, Teleport의 타겟이었던 #deleteModal 내에 엘리먼트가 렌더링되는 걸 볼 수 있다.

 

확실히 Composition API가 Vue3의 가장 큰 변경점이다. 이외엔, Teleport, Suspense 와 같이 부가기능 컴포넌트들이 주를 이루었다.

다만, 가상DOM 트리를 개선한 부분이 기존 Vue2는 어땠고, Vue3는 어떻게 개선되었는지 한번쯤 짚고 넘어갈 것을 권장한다.

Redux

1. 스토어 (Store): Redux에서는 애플리케이션의 상태를 담고 있는 단일 객체인 스토어를 사용한. 스토어는 애플리케이션의 전역 상태를 관리하며, 모든 상태 변경은 스토어를 통해 이루어진다.

2. 액션 (Action): 상태 변경을 위한 명령어를 나타내는 객체입니다. 액션은 반드시 type 속성을 가지고 있어야 한다.

3. 리듀서 (Reducer): 현재 상태와 액션을 받아 새로운 상태를 반환하는 순수 함수입니다. 애플리케이션의 전역 상태가 어떻게 변경되는지를 정의 한다.

4. 디스패치 (Dispatch): 액션을 리듀서로 보내 상태를 변경하는 것을 의미합니다. store.dispatch(action) 형태로 사용된다.

5. 컨테이너 컴포넌트 (Container Components): Redux와 연결되어 상태를 관리하는 리액트 컴포넌트이다.

 

리덕스 동작 원리

  • 리덕스의 스토어는 Context로 구성되어 Provider 컴포넌트의 value props로 전달된다.
  • 해당 Context는 구체적으로 { store, subscription }과 같은 형태를 띠고 있다.
  • connect() 함수에 의해 만들어진 컴포넌트나 useSelector() 함수를 사용한 컴포넌트는 액션이 디스패치 된 후 (어떠한 구독 메커니즘에 의해) 강제적으로 리렌더링이 유발된다. 이때 해당 컴포넌트는 Provider 컴포넌트가 제공해주는 리덕스 스토어의 상태를 읽는다.
  • 참고로 스토어는 늘 참조값이 같은 가변 객체에 해당하며, Provider 컴포넌트는 중간에 스토어가 다른 걸로 바뀌지 않는 이상 늘 같은 참조값을 가지는 객체를 Context로 구성하도록 구현된다. 따라서 액션을 디스패치 하든 무엇을 하든, Provider 컴포넌트에 의해 자식 컴포넌트들이 전부 리렌더링 될 일은 없다. 즉, 리렌더링은 오로지 구독 메커니즘에 의존하여 발생할 뿐이다.


Redux-Thunk

1. Thunk: Redux-Thunk은 비동기 작업을 처리하기 위해 사용되는 미들웨어 중 하나입니다. Thunk는 함수 형태로 감싸진 액션을 다룰 수 있게 해준다.

2. 비동기 작업 처리: Thunk를 이용하면 액션 생성자에서 비동기 작업을 수행하고, 작업이 완료된 후에 액션을 디스패치할 수 있다.

3. 단점: 복잡한 애플리케이션에서 비동기 코드가 늘어날수록 코드의 복잡성이 증가할 수 있다.

 

 

 


Redux-Saga 사가 (= 제네레이터 함수)

1. Generator 함수: Redux-Saga는 Generator 함수를 이용하여 복잡한 비동기 흐름을 제어하는 미들웨어이다.

2. Side Effects 처리: Redux-Saga를 사용하면 애플리케이션의 사이드 이펙트(네트워크 요청, 로깅 등)를 쉽게 다룰 수 있다.

3. 비동기 제어: 액션을 모니터링하고, 특정 액션이 발생할 때 비동기 작업을 시작하거나 중단할 수 있다.

4. 장점: 코드의 가독성이 뛰어나며, 비동기 작업을 더 선언적이고 효과적으로 관리할 수 있다.

이렇게 Redux, Redux-Thunk, 그리고 Redux-Saga는 각각의 특징을 가지고 있으며, 상황에 따라 선택하여 사용하면 된다. Redux는 상태 관리의 기본이 되는 라이브러리이며, Thunk와 Saga는 비동기 작업을 다루는데에 있어 각각의 장단이 있다.


이펙트

  • 사가를 실행하는 실행부에게 어떠한 동작을 수행해야 할지 알려주는 일반 객체(Plain Object)이다.
  • 일반적으로 put(), call(), select() 등의 헬퍼 함수를 호출함으로써 해당 이펙트 객체를 생성한다.
  • 이펙트 자체는 단순 객체이므로 아무런 사이드 이펙트를 발생시키지 않는다. 따라서 테스트가 용이하다.

 

주요 이펙트

  • call(fn, ...args) : (비동기 혹은 동기) 함수 fn을 호출한다.
  • select(selector) : selector를 이용하여 리덕스 스토어의 상태를 읽어온다. (= store.getState() 함수)
  • put(action) : action을 디스패치 한다. (= store.dispatch() 함수)
  • take(actionType) : actionType의 액션이 디스패치 될 때까지 기다린다.
  • fork(saga, ...args) : 새로운 실행 맥락으로 saga를 실행한다.
  • takeEvery(actionType, saga, ...args) : 사가를 하나 fork 하는 헬퍼 함수이다. 해당 사가는 actionType의 액션을 기다렸다가 saga를 fork 하는 작업을 무한히 반복한다.




Atom: 상태의 기본 단위
리액트 리코일에서는 Atom이라는 개념이 핵심입니다. 각 Atom은 고유한 키와 초기값을 가지고 있어 해당 키를 통해 상태에 접근할 수 있다. 아래는 간단한 Atom의 예시다.

 

import { atom } from 'recoil';

export const myState = atom({
  key: 'myState', // 고유한 키
  default: '', // 초기값
});

 

Selector: 파생된 상태 계산
Selector는 Atom에서 파생된 값을 계산하거나 다른 Selector에서 파생된 값을 계산하는 데 사용된다. 이를 통해 복잡한 상태 로직을 간단하게 처리할 수 있다.

import { selector } from 'recoil';
import { myState } from './atoms';

export const myDerivedState = selector({
  key: 'myDerivedState',
  get: ({ get }) => {
    const originalState = get(myState);
    // 상태에 대한 계산 로직
    return transform(originalState);
  },
});

 

RecoilRoot: 전역 상태 관리
RecoilRoot은 Recoil 애플리케이션의 상태를 관리하기 위한 컨테이너 역할을 한다. 애플리케이션 최상위 컴포넌트에서 RecoilRoot을 사용하여 전역 상태를 관리한다.

import { RecoilRoot } from 'recoil';

function App() {
  return (
    <RecoilRoot>
      {/* 애플리케이션 컴포넌트들 */}
    </RecoilRoot>
  );
}

 

사용 예시: 간편한 상태 관리

import { useRecoilState, useRecoilValue } from 'recoil';
import { myState, myDerivedState } from './atoms';

function MyComponent() {
  const [state, setState] = useRecoilState(myState);
  const derivedState = useRecoilValue(myDerivedState);

  const handleChange = (e) => {
    setState(e.target.value);
  };

  return (
    <div>
      <input type="text" value={state} onChange={handleChange} />
      <p>Derived State: {derivedState}</p>
    </div>
  );
}

 

이렇게 리액트 리코일을 사용하면 복잡한 상태 관리도 간편하게 처리할 수 있다.

 

공식 문서: https://recoiljs.org/

기존 작업을 하다가 리팩토링을 추가하였다.

기존의 코드는 https://asdf1326.tistory.com/21 전에 글로 올려논 부분이고 이부분을 리팩토링을 진행하였다.

 

작업 계기는 현 회사에서 불편한 api 호출 방식으로 사용 중인 부분이 있어서 이부분을 개선 작업에 들어갔다.

 

현회사에서는 src 에 바로 axios 폴더가 들어있었고 그안에 post get put 등등 들어있었다.

현재 가장 많이 사용하는 post get 에대한 부분만 따로 리팩토링을 진행하였다.

 

기존 코드

import axios from 'axios';
import * as process from 'process';

export const axiosClient = axios.create({
  headers: {
    'Content-type': 'application/json; charset=UTF-8',
    accept: 'application/json,',
  },
});

axiosClient.interceptors.request.use(
  (config) => ({
    ...config, withCredentials : true,
  }),
  (error) => Promise.reject(error)
);

axiosClient.interceptors.response.use(
  async (response) => {
    return response;
  },
  (error) => {
    const errorMessage = error.response.data;
    throw errorMessage;
  }
);
export type AxiosMethod = 'get' | 'put' | 'post' | 'delete' | 'patch';

//T: tax-account-console C: company
export type prefixUrl = 'T' | 'C' | 'CT' | 'HOS' | 'U' | 'local' | 'N';

export async function callAxios(
  url: string,
  method: AxiosMethod,
  prefixUrl: prefixUrl,
  data?: any,
  customContentType?: string
) {
  let resData: any;

  //url이 있을경우 데이터 요청한다.
  if (url !== undefined && url.length > 0) {
    //prefix를 붙인다.
    let apiUrl = `${process.env.NEXT_PUBLIC_CLIENT_PROXY_URL}`;

    switch (prefixUrl) {
      case 'T':
        apiUrl += process.env.NEXT_PUBLIC_TAX_ACCOUNT_URL + url;
        break;

      case 'C':
        apiUrl += process.env.NEXT_PUBLIC_COMPANY_URL + url;
        break;

      case 'CT':
        apiUrl += process.env.NEXT_PUBLIC_CONTENTS_URL + url;
        break;

      case 'HOS':
        apiUrl += process.env.NEXT_PUBLIC_HOS_URL + url;
        break;

      case 'U':
        apiUrl += process.env.NEXT_PUBLIC_USER_ACCOUNT_URL + url;
        break;

      case 'N':
        apiUrl += process.env.NEXT_PUBLIC_CLIENT_NOTIFICATION + url;
        break;
      case 'local':
        apiUrl += 'http://4954-61-74-228-131.ngrok-free.app/' + url;
        break;

    }

    switch (method) {
      case 'get':
        resData = await axiosClient.get(apiUrl, {
          params: data,
        });
        break;

      case 'post':
        resData = await axiosClient.post(apiUrl, data, {
          headers: {
            'Content-Type': customContentType || 'application/json; charset=UTF-8',
          },
        });
        break;


      case 'put':
        resData = await axiosClient.put(apiUrl, data);
        break;

      case 'patch':
        resData = await axiosClient.patch(apiUrl, data);
        break;

      case 'delete':
        resData = await axiosClient.delete(apiUrl, {
          data,
        });
        break;

      default:
        break;
    }
  }

  return resData.data;
}

//header에서 다운로드 파일 이름
const downloadFileName = (res: any) => {
  const disposition = res.headers['content-disposition'];
  const fileName = decodeURI(
    disposition
      .match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/)[1]
      .replace(/['"]/g, '')
  );
  return fileName;
};

export type ExcelParamType = {
  url: string;
  prefixUrl?: prefixUrl;
  data?: any;
};

export interface ExcelParamSetterProps {
  setExcelParam: ({ url, data, prefixUrl }: ExcelParamType) => void;
}

//파일 다운로드
export function callFileDownloadAxios(
  url: string,
  prefixUrl?: prefixUrl,
  data?: any
) {
  if (url !== undefined && url.length > 0) {
    //prefix를 붙인다.
    let apiUrl = `${process.env.NEXT_PUBLIC_CLIENT_PROXY_URL}`;

    switch (prefixUrl) {
      case 'C':
        apiUrl += process.env.NEXT_PUBLIC_COMPANY_URL + url;
        break;

      case 'CT':
        apiUrl += process.env.NEXT_PUBLIC_CONTENTS_URL + url;
        break;

      case 'HOS':
        apiUrl += process.env.NEXT_PUBLIC_HOS_URL + url;
        break;

      case 'U':
        apiUrl += process.env.NEXT_PUBLIC_USER_ACCOUNT_URL + url;
        break;
    }

    axiosClient
      .get(apiUrl, {
        responseType: 'blob',
        params: data,
      })
      .then((res) => {
        const downloadUrl = window.URL.createObjectURL(new Blob([res.data]));
        const link = document.createElement('a');
        link.href = downloadUrl;
        link.download = downloadFileName(res);
        document.body.appendChild(link);
        link.click();
        link.remove();
      });
  }
}

//파일 업로드
export async function callFileUploadAxios(url: string, data: any) {
  const response = await axiosClient.post(url, data, {
    headers: {
      'Content-Type': 'multipart/form-data',
    },
  });

  return response.data;
}

 

 

기존 코드는 위에같이 적혀있었다. 각 res,req 에대한 타입지정은 없었으며 다른 페이지에서도 지저분하게 적히며 전혀 관리가 안되는 부분들이 많이 차지하였다. 

 

위에 코드처럼 중구 난방으로 관리가 덜된 느낌이 강한 코드들이 퍼져있었다.

처음에 와서 스타트업 특성상 빠른 업무가 우선시 되며 최적화된 코드보다는 기능만 구현하기 위하여작업된 코드와... 신입이 만든.. 구조덕에 난해한 코드가 작성이 되어있었다....정말 고생 많이 했다...

 

이를 계속 쓰다보면 정리되어있지 않는 상태로 계속 작성이 되면 추후에는 리팩토링이 아니라 리뉴얼 정도의 작업이 되는걸 막기 위하여 타입을 정의 하며 어떻게든 백엔드와 인터페이스 정의를 같이 하여 맞추어나가려고 노력했다....

(같이 일한 백엔드 개발자의 노고가 정말 컷다..ㅠ) 

 

일단 가장 정리하기 쉬운 공지사항 부분먼저 차차 잡아나가려고 해당 구조를 잡았다.

common 에 넣은 이유는 공통적으로 관리에 앞서 구분 하기 위함으로 구조를 만들었다. 

 

api 서버단에서 dto 요청을 미리 클라이언트가 요청할 수 있는 상황을 대비하여 다음과 같이 구조를 짯다.

실제 서비스 엔드포인트에 이름을 따와서 정리를 해주었고 dto 파일을 작성한다.

 

 

인터페이스를 지정해주고 응답값을 받을 부분을 적어준다.

 

인터페이스 구현

 

 

함수 생성 (토큰 관련 작업이 추후에 바뀔 가능성이 있다고 하셔서 그부분을 예비로 작성하였다.)

더많은 작업이 필요하지만 대충 리프레쉬 토큰을 가지고 다시 액세스 토큰을 발급 받는 과정을 적을 수있게 미리 공간을 만들어둔다.

 

useQuery 도 추가적으로 관리 할 수 있도록 추가해준다.

 

이제 모든 인터페이스 정의는 끝났다 실제로 사용할 수 있게 다른페이지에서 호출 해본다.

 

 

notice.ts 는 실제 호출 가능한 함수를 담는 공간으로 작성한다.

 

이부분은 api 엔드포인트들을 관리하는 공간이다.

위와 같이 만들어준다.

 

useQuery 를 사용하여 작성한 코드

 

useQuery 가 필요없을 경우 일반적인 코드 작성 방법이다.

 

전체 코드는 토이프로젝트로 따로 생성 해두었다.

https://github.com/asdf132645/submoudleReact

 

GitHub - asdf132645/submoudleReact

Contribute to asdf132645/submoudleReact development by creating an account on GitHub.

github.com

 

+ Recent posts