내일배움캠프 React_7기 TIL - 27. Context, 커스텀훅, 라우터 실습

·2024년 11월 13일
1

영화 정보 사이트를 주제로 Context, 커스텀훅, 라우터를 실습했다.
실습하면서 배운 내용들을 정리하고자 한다.

.env 파일

TMDB API를 사용하여 영화 정보를 가져온다.
API KEY를 js파일 내에서 직접 선언하고 GitHub 같은 공개 저장소에 업로드되면, 보안이나 기타 등등 문제가 발생한다.
따라서 .env (환경 변수 파일) 파일을 생성하여 민감한 데이터들을 관리하고, import.meta.env로 접근하여 사용할 수 있다!
.env 파일은 /root 에 위치시키도록 하자.

//tmdb.js
const API_KEY = import.meta.env.VITE_TMDB_KEY;
//.env
VITE_TMDB_KEY = YOUR_API_KEY

⭐ GitHub에 프로젝트를 업로드할 때, .gitignore 파일에 .env 를 추가하여 .env 파일을 Git에서 무시하도록 설정하여야 한다. 이렇게 하면 해당 파일은 Git에 의해 버전 관리되지 않고, 저장소에 푸시되지 않는다.

Context, Custom Hook

user를 받는다는 가정 하에 실습하기 위해 sessionStorage에 임의의 고정 user값을 저장해놓고, user의 유무로 사용자에가 다른 화면을 보여주도록 할 것이다.

const AuthContext = createContext();

export const AuthProvider = ({ children }) => {
  const [user, setUser] = useState(() => {
    const savedUser = sessionStorage.getItem("user");
    return savedUser ? JSON.parse(savedUser) : null;
  });

  const login = (userData) => {
    sessionStorage.setItem("user", JSON.stringify(userData));
    setUser(userData);
  };

  const logout = () => {
    sessionStorage.removeItem("user");
    setUser(null);
  };

  return (
    <AuthContext.Provider value={{ user, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
};

AuthContext Context 객체를 만들고, AuthProvider 컴포넌트에서 상태와 로그인, 로그아웃 로직을 작성한다. {children}을 통해 Context를 하위 컴포넌트에서도 사용할 수 있도록 한다.

export const useAuth = () => useContext(AuthContext);

같은 파일 내에서 Custom Hook도 정의했다. useContext로 AuthContext의 상태, 로직을 가져와 useAuth()시 Context값을 쉽게 사용할 수 있도록 한다.
즉, useAuth()AuthContext에서 제공하는 값을 사용하기 쉽게 래핑(wrapping)한 Custom Hook이다.

Context가 준비되었으니, App.js에서 <AuthProvider> 자식 컴포넌트를 감싸 자식 컴포넌트들이 Context를 사용할 수 있도록 한다.

Router

Context로 user 상태와 로그인, 로그아웃 로직을 사용할 수 있기에 사용자에게 보여줄 화면을 user의 유/무 조건에 따라 지정해줄 것이다.

이를 위해

  • NonAuthenticatedRoute : 로그인하지 않은 사용자만 접근할 수 있는 컴포넌트를 감싸는 컴포넌트
  • ProtectedRoute : 로그인한 사용자만 접근할 수 있는 컴포넌트를 감싸는 컴포넌트
    를 만들어준다.
import { Outlet, Navigate } from "react-router-dom";
import { useAuth } from "../context/AuthContext";

function NonAuthenticatedRoute() {
  const { user } = useAuth();

  return !user ? <Outlet /> : <Navigate to="/profile" replace />;
}

export default NonAuthenticatedRoute;
import { Outlet, Navigate } from "react-router-dom";
import { useAuth } from "../context/AuthContext";

function ProtectedRoute() {
  const { user } = useAuth();

  return user ? <Outlet /> : <Navigate to="/login" replace />;
}

export default ProtectedRoute;

위에서 만든 useAuth 훅을 사용해 user 상태를 쉽게 접근하고, user의 유무로 <Outlet />(자식 라우트를 렌더링) 또는 <Navigate/>(특정 경로로 리디렉션)컴포넌트를 return 해준다.

NonAuthenticatedRoute는 user가 없을 때 <Outlet />을 반환해 로그인 페이지나 비회원 전용 페이지를 렌더링하고, user가 존재하면 /profile로 리디렉션하게 될 것이고,

ProtectedRoute는 user가 있을 때 <Outlet />을 반환해 프로필이나 보호된 콘텐츠를 렌더링하고, user가 없으면 /login으로 리디렉션하게 될 것이다.

App.js에서 위의 두 컴포넌트를 적용해주면 된다.

const App = () => {
  return (
    <AuthProvider>
      <Router>
        <Header />
        <main style={styles.mainContent}>
          <Routes>
            <Route path="/" element={<Home />} />

            <Route element={<NonAuthenticatedRoute />}>
              <Route path="/login" element={<Login />} />
            </Route>

            <Route element={<ProtectedRoute />}>
              <Route path="/profile" element={<Profile />} />
              <Route path="/popular-movies" element={<PopularMovies />} />
              <Route path="/latest-movies" element={<LatestMovies />} />
            </Route>
          </Routes>
        </main>
      </Router>
    </AuthProvider>
  );
};

/login 라우트 컴포넌트는 NonAuthenticatedRoute 컴포넌트로 감싸 /login으로 가기 전 NonAuthenticatedRoute 컴포넌트의 로직이 실행될 수 있도록 한다.

user 상태가 비어있는 경우, 어느 경로를 가던 /login 경로로 리다이렉션 하고 있는 것을 확인할 수 있다.

동일한 방법으로 회원 전용(user가 있는 경우) 페이지어야 하는 route 컴포넌트들은 ProtectedRoute 컴포넌트로 감싸준다.

로그인 이후에는 /popular-movies와 같은 하위 route 경로들로 잘 접근이 되는 것을 확인할 수 있다.

Custom Hook(2)

위에서 useAuth라는 커스텀 훅을 만들었다. 이번엔 영화 데이터를 쉽게 Fetch할 수 있는 useFetchMovies 훅을 만들어 영화 정보를 보여주는 컴포넌트에 사용할 것이다.

/hook/useFetchMovies.jsx를 만들어 코드를 작성할 것이다.

import { useEffect, useState } from "react";
import { fetchPopularMovies, fetchLatestMovies } from "../api/tmdb";

const useFetchMovies = (type) => {
  const [movies, setMovies] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const fetchMovies = async () => {
      setLoading(true);
      try {
        let data;
        if (type === "popular") {
          data = await fetchPopularMovies();
        } else if (type === "now_playing") {
          data = await fetchLatestMovies();
        }

        setMovies(data);
        setLoading(false);
      } catch (error) {
        console.log(error);
        setLoading(false);
      }
    };

    fetchMovies(); // 영화 데이터 가져오기 실행
  }, [type]); // type이 변경될 때마다 다시 실행

  return { movies, loading }; // 상태 반환
};

export default useFetchMovies;

useFetchMovies 훅은 type을 인자로 받는다. 내부의 fetchMovies 함수는 type 값에 따라 api 파일에서 불러온 적절한 API 호출 함수를 비동기로 호출하고, 반환된 데이터에 따라 movies와 loading 상태를 변경한다. fetchMovies 함수는 useEffect로 감싸져 있어, type이 변경될 때마다 자동으로 실행되도록 한다.
movies, loading 상태를 return하여 다른 컴포넌트에서 이 값들을 쉽게 읽을 수 있도록 한다.

//LatestMovies.jsx
import useFetchMovies from "../hooks/useFetchMovies";

const LatestMovies = () => {
  const { movies, loading } = useFetchMovies("now_playing");

  if (loading) {
    return <div>Loading...</div>;
  }

  return (
    <div style={styles.container}>
      <h1 style={styles.heading}>Latest Movies</h1>
      <div style={styles.movieGrid}>
        {movies.map((movie) => (
          <div key={movie.id} style={styles.movieCard}>
            <img
              src={`https://image.tmdb.org/t/p/w200${movie.poster_path}`}
              alt={movie.title}
              style={styles.movieImage}
            />
            <h2 style={styles.movieTitle}>{movie.title}</h2>
            <p style={styles.movieOverview}>{movie.overview}</p>
          </div>
        ))}
      </div>
    </div>
  );
};

최신 영화를 보여주는 LatestMovies 컴포넌트에서 useFetchMovies 훅을 사용한 코드이다. type으로는 now_playing 전달하여 적절한 API 함수를 호출하고, movies와 loading 값에 접근하여 Loading 처리와 movies 데이터를 보여주는 화면을 적절히 return해주고 있다.


정리 ☑️

오늘은
1. 프로젝트에서 API Key값 등의 민감한 정보를 관리하는 방법과,
2. 상태 값에 따라 사용자에게 적절한 화면을 제공하는 방법,
3. Custom Hook 사용법에 대하여 실습하였다.
오늘 배운 내용들을 앞으로의 프로젝트에 적절히 활용하면 더 좋은 퀄리티의 프로젝트를 진행 할 수 있을 것 같다.

profile
내배캠 React_7기 이수중

1개의 댓글

comment-user-thumbnail
2024년 11월 14일

우앙 저는 저거 다 못했는데 구현을 정말 잘하셨네요!! o(^0^)o

답글 달기

관련 채용 정보