리액트 - API 활용 및 리액트 쿼리

심영민·2025년 5월 2일
0

유레카

목록 보기
31/33

이번 포스팅에서는 리액트를 활용하여 날씨 api를 불러와 날씨앱을 간단히 구현해 보았다.

여기서 중요한 개념들을 정리해보겠다.

1. .env 파일을 활용한 환경 변수 관리

  • 목적: API 키와 같은 민감 정보를 코드 외부에 안전하게 보관하는 방법

  • 파일 생성: 프로젝트 루트 경로에 .env 파일 생성 필요

  • 변수명 규칙: Vite 빌드 환경에서는 변수명 앞에 VITE_ 접두사 사용 규약 준수

  • API 키 등록 예시:

    VITE_OPENWEATHER_API_KEY=발급받은_자신의_API_키_입력
  • 코드 접근: 애플리케이션 코드 내에서 import.meta.env.VITE_변수명 형태로 접근 가능 구조

2. OpenWeatherMap API 사용

  • 서비스 내용: 전 세계 도시 및 위치 기반의 실시간 날씨 정보 제공 서비스

  • API 키 발급: openweathermap.org 웹사이트 가입 후 My API Keys 메뉴에서 개인 API 키 발급 과정 진행

3. 데이터 페칭 라이브러리: Axios

  • 역할: 브라우저 및 Node.js 환경에서 사용 가능한 Promise 기반 HTTP 클라이언트 라이브러리

  • 설치 방법: npm 또는 yarn 패키지 매니저를 통한 설치 단계 필요

    npm install axios
  • 활용: 간결한 문법으로 HTTP 요청(GET, POST 등) 처리 용이성 제공 (fetch보다 편리함)

4. URLSearchParams 객체 활용

  • 기능: URL의 쿼리 스트링 파라미터를 생성하고 관리하는 데 유용한 내장 객체

  • 주요 용도: GET 요청 시 필요한 파라미터들을 동적으로 구성하는 상황에 적합

  • Axios와 함께 사용 예시:

    const params = new URLSearchParams();
    params.append('q', 'Seoul'); // 도시 이름 파라미터 추가
    params.append('appid', import.meta.env.VITE_OPENWEATHER_API_KEY); // API 키 추가
    params.append('units', 'metric'); // 측정 단위 추가
    
    axios.get(`https://api.openweathermap.org/data/2.5/weather?${params.toString()}`)
      .then(response => {
        // 응답 데이터 처리 로직
      })
      .catch(error => {
        // 에러 처리 로직
      });
    
    // Note: Axios의 params 옵션을 사용하면 URLSearchParams 객체 또는 일반 객체 전달 가능,
    // Axios가 자동으로 쿼리 스트링 생성
    axios.get(`https://api.openweathermap.org/data/2.5/weather`, {
      params: {
        q: 'Seoul',
        appid: import.meta.env.VITE_OPENWEATHER_API_KEY,
        units: 'metric'
      }
    })
    .then(...)
    .catch(...);

5. 도시 이름 기반 날씨 정보 가져오기

  • 필요 기능: 사용자로부터 입력받은 도시 이름과 OpenWeatherMap Current Weather API 조합

  • 구현 절차:

    1. 사용자 입력 필드 등으로부터 검색할 도시 이름 확보
    2. 획득한 도시 이름을 OpenWeatherMap Current Weather API 호출 파라미터(q)로 전달
    3. Axios 등으로 API 호출 실행 및 결과 데이터 처리
  • 코드 예시:

    • useWeatherApi.js

      const fetchWeatherByCityName = (cityName) => {
        if (!cityName) {
          console.log('도시 이름을 입력해주세요.');
          return; // 도시 이름 없으면 함수 종료
        }
        axios.get(`https://api.openweathermap.org/data/2.5/weather`, {
          params: {
            q: cityName,
            appid: import.meta.env.VITE_OPENWEATHER_API_KEY,
            units: 'metric'
          }
        })
        .then(response => {
          console.log(`${cityName} 날씨 데이터:`, response.data);
          // 날씨 데이터 상태 업데이트 또는 화면 표시 로직
        })
        .catch(error => {
          console.error(`${cityName} 날씨 정보 가져오기 에러:`, error);
          // 에러 상태 처리
        });
      };
      
      // 사용 예시 (예: 'Seoul' 검색)
      // fetchWeatherByCityName('Seoul');
    • WeatherPage.jsx

      import React, { useEffect, useState } from 'react'
      import css from './WeatherPage.module.css'
      import Button from './Button'
      import { useSearchParams } from 'react-router-dom'
      import { getCurrentDate, getCountryData } from './useWeatherApi'
      
      const WeatherPage = () => {
        const [searchParams, setSearchParams] = useSearchParams()
        const [weatherData, setWeatherData] = useState(null)
        const city = searchParams.get('city')
      
        const cityButtons = [
          { id: 'current', label: '현재위치' },
          { id: 'seoul', label: '서울' },
          { id: 'hongkong', label: '홍콩' },
          { id: 'new york', label: '뉴욕' },
          { id: 'paris', label: '파리' },
        ]
      
        // 데이터 바인딩
        useEffect(() => {
          const fetchWeatherData = async () => {
            try {
              let data
              if (city) {
                // city 파라미터가 있으면 해당 도시의 날씨 정보 가져오기
                data = await getCountryData(city)
              } else {
                data = await getCurrentDate()
              }
              // console.log('날씨 정보:', data)
              setWeatherData(data)
            } catch (err) {
              console.log('날씨 정보 로딩 에러:', err)
            }
          }
          fetchWeatherData()
        }, [city])
      
        const handleChangeCity= city => {
          if (city === 'current') {
            setSearchParams({})
          } else {
            setSearchParams({ city })
          }
        }
      
        return (
          <main>
            <h2>날씨 API 활용하기</h2>
            <div className={css.weatherInfo}>
              <p className={css.location}>
                {weatherData?.sys.country} / {weatherData?.name}
              </p>
              <div className={css.tamperature}>
                <p>{weatherData?.main.temp} &#8451;</p>
                <p>
                  <img
                    src={`http://openweathermap.org/img/wn/${weatherData?.weather[0].icon}.png`}
                    alt=""
                  />
                </p>
              </div>
            </div>
            <div className={css.buttonContainer}>
              {cityButtons.map(button => (
                <Button
                  key={button.id}
                  city={button.id}
                  label={button.label}
                  onClick={handleChangeCity}
                />
              ))}
            </div>
          </main>
        )
      }
      
      export default WeatherPage
      

6. TanStack React Query (리액트 쿼리)

  • React Query 개요:

    • React 애플리케이션의 서버 상태 관리에 특화된 라이브러리.

    • 기존 상태 관리 라이브러리(Redux, MobX)와 달리 서버 데이터 페칭, 캐싱, 동기화, 업데이트에 중점.

    • 클라이언트 상태(UI, 폼 입력 등)와 서버 상태(API 데이터, DB 데이터)를 명확히 구분하여 관리.

      그니까 요약하면 매번 useEffect, useState로 로딩중.. 데이터 가져오기.. 안해도됨!! 한번 가져온 데이터 알아서 저장하고 똑같은 데이터는 저장된걸로 바로 보여줌!! 데이터가 오래되면 자동 업데이트도 해줌!!

  • 주요 특징:

    • 자동 캐싱: 서버 데이터 자동 캐싱으로 불필요한 데이터 요청 방지.
    • 백그라운드 업데이트: 사용자 경험을 방해하지 않는 비동기 데이터 갱신.
    • 상태 관리: 로딩, 성공, 에러 상태 등 데이터 요청 라이프사이클 관리 용이.
    • 부가 기능: 페이지네이션, 무한 스크롤, 데이터 자동 재검증(윈도우 포커스, 네트워크 재연결 등) 지원.
  • 설치 및 기본 설정:

    • 설치: @tanstack/react-query, @tanstack/react-query-devtools (추천) 설치.

    • 기본 설정: 애플리케이션 루트에 QueryClientProvider를 사용하여 QueryClient 인스턴스 제공.

    • 기본 설정 예시:

      import { createRoot } from 'react-dom/client';
      import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
      import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
      
      const queryClient = new QueryClient({
        defaultOptions: {
          queries: {
            retry: 1,
            refetchOnWindowFocus: false,
          },
        },
      });
      
      createRoot(document.getElementById('root')).render(
        <QueryClientProvider client={queryClient}>
          {/* App components */}
          <ReactQueryDevtools initialIsOpen={false} /> {/* 개발 도구 */}
        </QueryClientProvider>
      );
  • 핵심 훅 (useQuery, useMutation):

    • useQuery:

      • 데이터 조회(GET) 작업 시 사용되는 훅.

      • 주요 파라미터: queryKey (쿼리 식별 유니크 키, 배열), queryFn (데이터 가져오는 비동기 함수), staleTime, cacheTime, enabled, retry 등.

      • 주요 반환 값: data (성공 데이터), isLoading (초기 로딩), isError (에러 발생), error (에러 객체), isFetching (백그라운드 로딩), refetch (수동 리페치 함수) 등.

      • useQuery 사용 예시:

        import { useQuery } from '@tanstack/react-query';
        
        export const useWeather = city => {
          return useQuery({
            queryKey: ['weather', city], // 쿼리 식별 키
            queryFn: async () => { // 데이터를 가져오는 함수
              try {
                const data = city ? await getCountryData(city) : await getCurrentDate();
                return data;
              } catch (error) {
                throw error;
              }
            },
            staleTime: 1000 * 60 * 5, // 5분간 데이터 신선 상태 유지
            retry: 1, // 실패 시 1번 재시도
          });
        };~~
    • useMutation:

      • 서버 데이터 변경(POST, PUT, PATCH, DELETE 등) 작업 시 사용되는 훅.

      • 주요 옵션/콜백: mutationFn (데이터 변경 비동기 함수), onSuccess, onError, onSettled 등 뮤테이션 라이프사이클 콜백.

      • 주요 반환 값: mutate (뮤테이션 실행 함수), isLoading (뮤테이션 실행 중), isError (에러 발생), error (에러 객체) 등.

      • useMutation 사용 예시:

        import { useMutation } from '@tanstack/react-query';
        import { queryClient } from './queryClient'; // QueryClient 인스턴스
        
        const createTodo = async (newTodo) => {
          const response = await fetch('/api/todos', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(newTodo),
          });
          if (!response.ok) throw new Error('Failed to create todo');
          return response.json();
        };
        
        export const useCreateTodo = () => {
          return useMutation({
            mutationFn: createTodo,
            onSuccess: () => {
              // 뮤테이션 성공 후 특정 쿼리 무효화 (예: todos 목록 쿼리)
              queryClient.invalidateQueries(['todos']);
            },
            onError: (error) => {
              console.error('Todo 생성 실패:', error);
            },
          });
        };
  • 데이터 흐름 및 상태:

    • 데이터 상태 주기: Fresh(신선), Stale(오래된), Inactive(비활성), Deleted(삭제됨).

    • 기본 동작:

      • 컴포넌트 마운트 시 데이터 요청 및 캐시 확인.
      • 데이터 획득 시 캐시 업데이트 및 컴포넌트에 데이터 제공.
      • 리렌더링 시 캐시된 데이터 즉시 사용.
      • 조건 충족 시(stale 상태, 윈도우 포커스 등) 백그라운드 자동 리페치.
      • 컴포넌트 언마운트 시 쿼리 Inactive 상태 전환.
      • cacheTime 경과 시 캐시에서 데이터 제거.
  • React Query 개발 도구로 접근

    • 브라우저 우측 하단 로고를 통해 접근.
    • 활성 쿼리 목록, 각 쿼리의 상태(fresh, stale, inactive 등) 및 데이터 내용 확인.
    • 쿼리 상태 변화 실시간 모니터링 기능.
    • 디버깅 편함
profile
코딩너무어려운대 어떡할과 재학중

0개의 댓글