항해 취업 리부트 코스 6기 (사전스터디)-심화 2

Hunter Joe·2024년 11월 1일
0

항해99

목록 보기
5/7

React에서 비동기 처리 방법

import React, { useState, useEffect } from "react";
import axios from "axios";

const App = () => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      setLoading(true);
      try {
        const response = await axios.get("http://localhost:4000/todos");
        setData(response.data);
      } catch (error) {
        setError(error);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, []);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <h1>Fetched Data</h1>
      <ul>
        {data &&
          data.map((item) => (
            <li key={item.id}>
              <h2>{item.title}</h2>
              <p>{item.isDone ? "Done" : "Not Done"}</p>
            </li>
          ))}
      </ul>
    </div>
  );
};

export default App;

적절한 UI를 보여주지만 다음과 같은 문제가 존재한다.

위 코드의 단점

  1. 상태 관리의 복잡성
    • 컴포넌트 내에서 직접 로딩, 에러, 데이터를 관리하는 상태 등 여러 상태를 직접 관리 해야 한다.
    • 상태 관리가 복잡하고, 각 상태에 따른 로직이 컴포넌트 내부에 분산되어 있어 코드가 복잡하다.
  2. 중복 코드
    • 여러 컴포넌트에서 동일한 데이터를 fetching 해야하는 경우, 각 컴포넌트마다 동일한 비동기 로직을 반복해서 작성해야 한다.
    • 코드 중복 = 유지보수성 저하
  3. 비즈니스 로직의 분리 부족
    • 비동기 로직이 컴포넌트 내부에 직접 포함되면, 비즈니스 로직과 UI로직이 혼합되어 가독성과 유지보수성을 낮춘다.

      비즈니스 로직이란?
      애플리케이션의 핵심 동작을 정의하는 코드

  4. 서버 상태 관리의 어려움
    • 서버 상태를 효율적으로 관리하기 어렵다.

      서버 상태 vs 클라이언트 상태란?
      클라이언트 상태 : UI와 관련된 일시적인 데이터
      서버 상태 : 서버에서 가져오는 데이터 (API 응답), 캐싱, 동기화, 등의 관리 필요

Redux 미들웨어

  • Redux Middleware : actionreducer에 도달하기 전에 중간에서 가로채서 추가적인 작업을 수행할 수 있게 해주는 함수
  • 이를 통해 비동기 로직을 처리(Redux Thunk, Redux Saga, Redux Observable..)히거나, 로그를 기록하거나 에러를 처리하는 등의 작업을 수행할 수 있다.

일반적인 Redux의 처리 방식

  1. UI 컴포넌트에서 dispatch가 일어난다.
  2. dispatch가 일어날 때 action 객체를 같이 dispatch한다.
  3. action 객체는 리듀서로 전달되어 Redux store를 업데이트 한다.

Redux Thunk의 처리 방식

  1. UI 컴포넌트에서 dispatch가 일어난다.

  2. dispatch가 일어날 때 thunk 함수를 dispatch한다.

    액션 객체가 아니라, 함수를 dispatch한다는 것이 핵심
    함수 안에서 여러가지 비동기 로직 처리 가능

  3. 전달된 Thunk 함수에 의해 Redux Thunk 미들웨어가 이 함수를 호출하고, 함수 안에서 비동기 작업을 수행한다.

  4. 비동기 로직이 수행된 이후 필요에 따라 액션 객체를 별도 생성하여 dispatch한다.

  5. dispatch된 액션 객체는 리듀서로 전달되어 Redux store를 업데이트 한다.

Redux Thunk의 장점

  1. 상태 관리의 일관성 : Thunk를 사용해 비동기 로직을 중앙에서 관리할 수 있다.
  2. 비즈니스 로직의 분리 : UI와 비즈니스 로직을 분리하여 유지보수성, 가독성 향상
  3. 로딩 및 에러 상태 관리 : 중앙에서 일관되게 관리할 수 있어, 상태 변화에 따른 UI 업데이트가 용이하다.

Reudx Thunk의 한계점

  1. 복잡성 증가 : 비동기 로직이 복잡해질수록 Action Creator와 Reducer의 코드가 길어지고, 보일러플레이트 코드(반복적 코드)가 많아져 유지보수가 어려워진다.
  2. 테스트가 복잡하다 : 비동기 로직을 포함한 Action Creator를 테스트하는 것이 복잡하다.

    Redux Thunk를 사용하면 비동기 로직이 액션 크리에이터에 포함되는데, 이를 테스트하기 위해서는 다양한 응답 상태(로딩, 성공, 실패)를 시뮬레이션해야 합니다. 이러한 비동기 작업을 모킹(mocking)하고, 상태 변화를 검증하는 테스트 코드를 작성하는 과정이 복잡해집니다. 또한, 비동기 로직과 관련된 여러 상태를 관리해야 하므로 테스트 코드의 양도 많아집니다.

TanStack Query

TanStack Query : 서버 상태 관리 라이브러리
Redux Thunk와 같은 문제들을 해결하기 위해 TanStack Query가 등장
TanStack Query는 서버 상태 관리에 특화 된 기능을 제공하여 복잡한 비동기 로직을 단순화 하고 캐싱, 동기화, 리페칭 등의 기능을 쉽게 구현할 수 있다.

서버 상태 관리 라이브러리

  • TanStack Query : 서버 상태 관리 라이브러리

주요기능

  1. 데이터 캐싱 : 동일한 데이터를 여러 번 요청하지 않도록 캐싱 → 성능 향상
  2. 자동 리페칭(re-fetching) : 바뀐 데이터를 자동으로 리페칭 → 최신 데이터 상태 유지
  3. 쿼리 무효화(Invalidation) : 특정 이벤트가 발생했을 때 쿼리를 무효화하고 데이터를다시 가져올 수 있다.

TanStack Query 사용하기

Installation

npm i @tanstack/react-query

QueryClientProvider

// main.jsx or index.js
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
import "./index.css";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

const queryClient = new QueryClient();

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

Core 기능

  1. useQuery
  2. useMutation
  3. invalidateQueries

useQuery

  • 데이터를 가져오고 캐싱하는 데 사용되는 기본 훅
  • 서버에서 데이터를 가져와 컴포넌트에서 사용 가능하게 하고, 자동으로 캐싱하여 리페치 시 성능을 최적화

1 queryKey

  1. queryFn

useMutation

  • 데이터를 추가, 수정, 삭제(CUD)하는 작업에 사용되는 훅.
  • 비동기 작업이 완료된 후에 관련된 쿼리를 무효화하는 과정이 포함되는데 이 역시 최신 데이터를 유지하는 데에 필수적인 요소
  1. mutationFn : 추가, 수정,삭제와 관련된 비동기 로직이 들어간다.
  2. isSuccess, isError등을 이용해 상태에 따른 관리를 할 수 있다.

invalidateQueries

  • 특정 쿼리를 invalidate해서 데이터를 다시 fetching 하게 하는 함수
  • 주로 useMutation과 함께 사용

TanStack Query의 Lifecycle

stale-while-revalidate(SWR)

SWR : 최신 데이터가 도착하기 전까지 기존 캐시 데이터를 사용하는 전략

TanStack Query의 데이터 흐름

그림 순서를 잘 볼 것

캐시 데이터에 대한 Lifecycle

상태설명
fresh데이터를 새로 패칭할 필요 없는 상태 staleTime이 지나지 않은 상태로, 캐시 데이터를 그대로 사용가능
stale데이터를 새로 패칭해야 하는 상태. staleTime이 지난 후로, 새로운 데이터를 가져오기 위해 쿼리가 실행됨
active현재 컴포넌트에서 사용 중인 쿼리 상태. 컴포넌트가 마운트되어 쿼리를 사용하고 있을 때
inactive더 이상 사용되지 않는 쿼리 상태. 컴포넌트가 언마운트되거나 쿼리가 더 이상 필요하지 않을 때
deleted캐시에서 제거된 쿼리 상태입니다. gcTime 이 지나면 쿼리가 캐시에서 삭제되어 이 상태가 됨
fetching데이터를 서버에서 가져오고 있는 상태. 이 상태에서는 isFetching이 true로 설정됩니다.

gc : garbage collection

default config

staleTime: 0
refetchOnMount: true
refetchOnWindowFocus: true
refetchOnReconnect: true
gcTime : 1000 * 60 * 5 (5분)
rety: 3 

select

select : 쿼리 함수에서 반환된 데이터의 특정 부분만 가져오거나, 데이터를 변환하는 작업을 할 수 있다. 단, 캐시 데이터는 원본 데이터를 유지한다.

import { useQuery } from 'react-query'

function User() {
  const { data } = useQuery({
	  queryKey: ["user"],
	  queryFn: fetchUser,
	  select: user => user.username
  });
  
  return <div>Username: {data}</div>
}
profile
두 or 다이 / FE 목표
post-custom-banner

0개의 댓글