새로운 블로그로 이전하였습니다!

비동기 통신은 현대의 웹 페이지에서 사용자에게 더 나은 사용성을 제공하기 위해 필수적으로 구현해야 하는 통신 방법 입니다.

동기 통신은 요청과 응답이 순서대로 진행되어서 서버에서 응답이 오기 전까지 브라우저에선 아무 기능도 수행하지 못해서 네트워크가 느린 사용자일 수록 부정적인 경험을 제공하게 됩니다.

하지만 비동기 통신은 화면이 렌더링되는 동안에도 데이터를 받아올 수 있고, 데이터 요청을 보낸 이후에도 자유롭게 동작 수행이 가능합니다.

요청을 보낸 이후 다른 작업을 수행하다가, 응답이 온 이후 결과를 보여주는 방식을 통해 사용자의 부정적 경험을 최소화 시킬 수도 있습니다.

JavaScript 에서는 XMLHttpRequest, Fetch API 등의 라이브러리를 통해 비동기 통신을 처리할 수 있습니다.

특히 리액트에서는 HTTP 통신 라이브러리를 통해 REST API 를 통한 데이터 통신을 처리할 수 있습니다.

이번 글에서는 JavaScript의 비동기 통신에 대한 소개 및 리액트에서 사용되는 HTTP 통신 라이브러리인 react-query, swr, alova 에 대해 비교해보겠습니다.

JavaScript 비동기 통신

XMLHttpRequest

const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://example.com/data.json');
xhr.send();
xhr.onreadystatechange = function() {
  if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
    const data = JSON.parse(xhr.responseText);
    // do something
  }
}

JavaScript에서 최초로 제공된 비동기 통신 방법 입니다.

옛날부터 만들어진 기능답게 IE7 버전에서도 문제없이 작동할 수 있는 장점이 있어서, 호환을 중요시하는 웹앱같은 경우 XHR을 사용하면 좋은 선택이 됩니다.

다만 오래된 기술답게 코드의 가시성이 현저히 떨어지고 보안이 취약하여 CSRF, XSS 공격 등에  쉽게 노출됩니다.

jQuery AJAX

$.ajax({
  url: "서버 URL",
  type: "HTTP Method GET POST 등",
  data: "서버에 전송할 데이터",
  success: function(response) {
    // Response 데이터 처리
  },
  error: function(xhr, status, error) {
    // Ajax 요청 실패 시 처리
  }
});

jQuery에서 제공되는 비동기 통신 방식 입니다.

위에 설명한 XMLHttpRequest 방식 대비 쉽고 간단해서 성공/실패시에 대한 처리를 편리하게 할 수 있고, 체이닝 방식을 지원해서 요청이 성공 했을 경우 다음 처리의.. 다음처리의.. then().then().then() 등의 구현이 가능합니다.

또한 JSON, XML, Text 등 다양한 데이터 타입도 지원합니다.

 

Fetch API

async function fetchData() {
  try {
    const response = await fetch('url');
    const data = response.json();
    console.log(data);
  } catch (error) {
    console.error(error);
  }
}

fetchData();

Fetch API는 Promise 기반으로 동작하는 방식 입니다.

async/await 문법을 제공하여 위의 방법보다 코드가 더 간결해지고 더 나은 가독성을 제공합니다.

또한 CORS 를 지원하여 서로 다른 도메인의 리소스에 접근할 수 있습니다.

Promise는 비동기 작업을 처리하기 위한 객체로, I/O가 완료되면 Promise에 감싸져서 반환되고 then() 혹은 await 으로 풀은 뒤에 데이터를 꺼내올 수 있습니다.

이 때 then() 혹은 async/await 구문에 작성된 코드는 micro task queue 에 담기게 되어 task queue에 있는 코드가 모두 실행된 이후에 작업이 수행되게 됩니다.

console.log('start'); // 1번

// Macro Task
setTimeout(() => {
  console.log('setTimeout'); // 5번
}, 0);

// Micro Task
Promise.resolve(
	console.log("Request"); // 2번
	).then(() => {
  console.log('Promise'); // 4번
});

console.log('end'); // 3번

/*
start
Request
end
Promise
setTimeout
*/

JavaScript 의 Event Roop, Task Queue에 대해 움짤과 함께 쉽게 설명해놓은 블로그가 있어서 아래를 참고하면 이해하기 더 좋을 것 같습니다

https://velog.io/@titu/JavaScript-Task-Queue말고-다른-큐가-더-있다고-MicroTask-Queue-Animation-Frames-Render-Queue

 

Promise는 3가지 상태값을 가집니다.

  • Pending : 작업 진행 전
  • Fulfilled : 작업 완료
  • Rejected : 작업 실패

Promise의 생성자 함수에는 `resolve()` 와 `reject()` 콜백 함수가 전달됩니다. 작업이 완료되면 resolve() 함수를 호출하고, 작업이 실패하면 reject() 함수를 호출합니다.

또한 Node.js에서도 사용이 가능하여 서버단에서도 동일한 코드로 네트워크 요청 처리가 가능합니다.

다만 IE 같은 구버전 브라우저에서는 지원이 안된다는 단점이 있습니다.

axios

async function postRequest() {
  try {
    const response = await axios.post('https://example.com/api/endpoint', {
      data: 'example data'
    });
    console.log(response.data); // 응답 결과 출력
  } catch (error) {
    console.error(error);
  }
}

postRequest();

XMLHttpRequest 기반으로 브라우저 호환성이 높으며, 사용방법이 불편한 단점을 개선시킨 라이브러리 입니다.

Async/Await 문법을 지원하며, get() / post() 같이 요청하는 http method 따라 함수를 제공하여 코드 가독성을 높일 수 있습니다.

또한 HTTP Status 및 Error에 따른 핸들링이 가능하고, Req/Res Interceptor 기능을 제공해서 요청/응답 메시지에 헤더 정보를 추가하거나 변경하는 작업이 가능합니다.

axios Fetch API
외부 라이브러리로 추가 네트워킹 필요 ( 11KB ) 브라우저 내장
XML HTTP Request 기반으로 IE 7 지원 IE 지원 X
JSON 자동 파싱 .json() 함수를 통해 파싱 필요
HTTP Status 코드를 통한 핸들링 200 ~ 299 까지 ok() 만 뱉고 끝
XRSF 보호  
Req/Res Intercept  
Timeout  



React Query vs SWR

리액트에서 비동기 통신을 하기 위해서는 상태 관리가 필요합니다. 상태관리를 통해 로딩,에러,결과를 나눠 API 호출을 처리하고, 반환된 데이터를 컴포넌트에서 사용할 수 있습니다.

최근 React-Query와 SWR 같은 비동기 통신 상태관리 라이브러리가 인기를 끌고 있습니다.

위 라이브러리들은 캐싱을 통해 API 호출을 최적화 하고, TypeScript와 같은 다른 언어를 지원하여 코드 안정성을 향상시키면서, 개발자의 코드 이해도를 높일 수 있습니다.

아래부턴 React-Query와 SWR의 특징에 대해 차이점 비교를 중심으로 살펴보겠습니다.

사이즈

React-Query 55kb, swr: 10kb 로 React Query가 훨씬 무겁습니다.

다만 그만큼 부가적인 기능들 많이 제공합니다. 기능들은 아래에 작성해 두었습니다.

데이터 캐싱

React-Query : API 호출을 최소화 하기 위해 받아온 데이터를 캐싱한 후 재활용 합니다. 위를 사용하기 위해선 staleTime 설정을 변경해주면 됩니다. ( default : 0 )

또한 설정해둔 시간 ( staleTime / cacheTime )이 지나면 자동으로 Gabege Collection이 데이터를 수집 후 폐기합니다.

SWR : 캐시가 만료된 경우 데이터를 가져오기 위함으로 사용됩니다.

상태 관리

React-Query : 외부 상태관리 라이브러리 ( Redux, Recoil )과 함께 사용이 가능합니다.

SWR : React Hooks를 사용하여 컴포넌트 내에서만 상태관리 됩니다.

Provider

React-Query

React Query를 사용하기 위해선 별도의 Provider를 필수로 App 컴포넌트 상위에서 마운트해야 합니다.

import { QueryClient, QueryClientProvider } from 'react-query';
import App from './App';

const queryClient = new QueryClient();

ReactDOM.render(
  <QueryClientProvider client={queryClient}>
    <App />
  </QueryClientProvider>,
  document.getElementById('root')
);

QueryClient 의 값을 변경하여 캐시전략 및 각종 설정을 변경할 수 있습니다.

설정 : https://tanstack.com/query/latest/docs/react/reference/QueryClient

또한 DevTools를 지원하여 브라우저 단에서 캐시된 데이터를 탐색하거나, 변경할 수 있습니다.

 

 ReactDOM.render(
  <QueryClientProvider client={queryClient}>
  	<ReactQueryDevtools initialIsOpen={false} />
    <App />
  </QueryClientProvider>,
  document.getElementById('root')
);

SWR

SWRConfig 라는 설정이 존재해서 App 컴포넌트 위해 마운트해야하지만 필수는 아닙니다.

Mutation

React-Query

 useMutate 를 통해 Creat/Update/Delete 작업을 보낼 경우 useQuery를 통해 Read 해온 데이터는 자동으로 API 호출을 통해 데이터를 업데이트 합니다.

 

swr

자동으로 업데이트 되지 않고 캐시된 데이터를 보여주기 때문에 mutate 함수를 통해 직접 업데이트 해주거나 useSWR 훅을 한번 더 호출해줘야 합니다.

Selector

React-Query 애서는 select 를 통해 Response Data 를 수정하는 기능을 제공합니니다.

const { data, isLoading, isError } = useQuery('todos', fetchingFunc, {
    select: (data) => {
      // 받아온 데이터의 completed 속성 값을 모두 false로 변경하여 반환합니다.
      return data.map((todo) => ({ ...todo, completed: false }));
    },
  })

확장성

React-Query는 REST, GraphQL, gRPC, WebSocket 등 다양한 데이터 소스를 지원하는 방면

swr은 REST 만 지원합니다.

 

결론

최대한 가벼운 사이즈, 별다른 커스텀이 필요없고 REST 방식의 통신만 사용하는 프로젝트의 경우 SWR

데이터 캐싱, 전역 상태관리 라이브러리와의 연계, REST 외의 데이터 소스 지원이 필요할 경우 React-Query가 좋은 선택이 될 것 같습니다.

복사했습니다!