자바스크립트를 허용해주세요.
[ 자바스크립트 활성화 방법 ]
from Mohon Aktifkan Javascript!
 

[React] React TMDB API를 사용한 리듀서 규현하기

728x90

 

TMDB API를 사용한 React에서 Redux 리듀서를 설정하는 방법에 대해 알아보겠습니다. 먼저 아래 사이트로 들어가 TMDB API로그인과 API Key 발급이 필요합니다.

 

The Movie Database (TMDB)

환영합니다 수백만 개의 영화, TV 프로그램 및 인물을 발견하세요. 지금 살펴보세요.

www.themoviedb.org

 

사이트 로그인 후 설정 API로 들어가 API Key를 발급하면 위 사진처럼 API 토큰과 키가 발급됩니다. 다음 아래 명령어로 React 프로젝트를 만들면 됩니다.

 npx create-react-app tmdb-blog

1.Redux 설정

프로젝트 폴더에 actions 폴더를 따로 만들고 types.js 파일에 액션타입을 정의합니다.

// src/actions/types.js
export const FETCH_MOVIES = "FETCH_MOVIES";

 

src/actions/movieActions.js 파일을 만들고 아래 코드로 영화를 가져오는 액션 생성자를 정의합니다.

// src/actions/movieActions.js 
import { FETCH_MOVIES } from "./types";

export const fetchMovies = () => async (dispatch) => {
  const response = await fetch(
    `https://api.themoviedb.org/3/movie/popular?api_key=your_apikey`
  );

  const data = await response.json();

  dispatch({
    type: FETCH_MOVIES,
    payload: data.results || [],
  });
};

 

다음 src/actions/movieReducer.js 파일을 생성하고 리듀서를 생성하여 상태를 관리합니다.

// src/reducers/movieReducer.js
import { FETCH_MOVIES } from "../actions/types";

const initialState = {
  movie: [],
};

const movieReducer = (state = initialState, action) => {
  switch (action.type) {
    case FETCH_MOVIES:
      return {
        ...state,
        movies: action.payload,
      };
    default:
      return state;
  }
};

export default movieReducer;

 

다음 src 폴더에 store.js 파일을 생성하고 react-redux와 redux 라이브러리를 설치후 아래 코드를 입력합니다.

// react-redux redux 라이브러리 설치
npm install -g react-redux & redux 
yarn add -D react-redux & redux
// react-thuck 라이브러리 설치
yarn add -D redux-thunk

// @reduxjs/toolkit 설치(선택 사항)
yarn add -D @reduxjs/toolkit

// src/store.js
import { configureStore } from "@reduxjs/toolkit";
import movieReducer from "./reducer/movieReducer";

const store = configureStore({
  reducer: {
    movies: movieReducer,
  },
});

export default store;

 

이제 리덕스 스토어를 React 컴포넌트에 사용합니다.

// src/components/MovieList.js
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { fetchMovies } from "../actions/movieActions";
import { Link } from "react-router-dom";

const MovieList = () => {
  const dispatch = useDispatch();
  const movies = useSelector((state) => state.movies.movies);

  useEffect(() => {
    dispatch(fetchMovies());
  }, [dispatch]);

  return (
    <div>
      <h1>Popular Movies</h1>
      <ul>
        {Array.isArray(movies) && movies.length > 0 ? (
          movies.map((movie) => (
            <li key={movie.id}>
              <Link to={`/movie/${movie.id}`}>{movie.title}</Link>
            </li>
          ))
        ) : (
          <li>No Movies</li>
        )}
      </ul>
    </div>
  );
};

export default MovieList;

 

마지막으로 App.js로 가서 Provider를 사용한 스토어를 앱에 제공합니다.

import React from "react";
import { Provider } from "react-redux";
import store from "./store";
import MovieList from "./components/MovieList";

const App = () => {
  return (
    <Provider store={store}>
      <MovieList />
    </Provider>
  );
};

export default App;

 

MovieList의 포스터를 나열하려면 아래 코드를 입력합니다.

import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { fetchMovies } from "../actions/movieActions";
import { Link } from "react-router-dom";

const MovieList = () => {
  const dispatch = useDispatch();
  const movies = useSelector((state) => state.movies.movies);

  useEffect(() => {
    dispatch(fetchMovies());
  }, [dispatch]);

  return (
    <div>
      <h1>Popular Movies</h1>
      <ul>
        {Array.isArray(movies) && movies.length > 0 ? (
          movies.map((movie) => (
            <li key={movie.id}>
              <Link to={`/movie/${movie.id}`}>
                {movie.poster_path && (
                  <img
                    src={`https://image.tmdb.org/t/p/w500${movie.poster_path}`}
                    alt={movie.title}
                    className="movie-poster"
                  />
                )}
                <Link to={`/movie/${movie.id}`}>{movie.title}</Link>
              </Link>
            </li>
          ))
        ) : (
          <li>No Movies</li>
        )}
      </ul>
    </div>
  );
};

export default MovieList;

2. 스타일링 추가하기 

CSS 파일을 생성해 포스터와 제목의 스타일을 설정해줍니다.

// src/style.css
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

.movie-list {
  list-style-type: none;
  padding: 0;
  display: flex;
  flex-wrap: warp;
}

.movie-item {
  margin: 10px;
  text-align: center;
}

.movie-link {
  text-decoration: none;
}

.movie-poster {
  width: 150px;
  height: auto;
  border-radius: 8px;
}

.movie-title {
  margin-top: 5px;
}

3. Javascript 스크롤 구현

MovieList.js에서 포스크 클릭 시 스크롤 구현을 위한 이벤트 핸들러를 추가합니다. 기본적으로 스크롤을 제어하기 위해 scrollIntoView 메서드를 사용합니다.

import React, { useEffect, useRef } from "react";
import { useDispatch, useSelector } from "react-redux";
import { fetchMovies } from "../actions/movieActions";
import { Link } from "react-router-dom";
import "../style.css";

const MovieList = () => {
  const dispatch = useDispatch();
  const movies = useSelector((state) => state.movies.movies);
  const movieListRef = useRef(null); // 영화 리스트의 ref

  useEffect(() => {
    dispatch(fetchMovies());
  }, [dispatch]);

  const handleScroll = (index) => {
    const item = movieListRef.current.children[index];
    item.scrollIntoView({ behavior: "smooth", inline: "center" }); // 중앙으로 스크롤
  };

  return (
    <div>
      <h1 className="movie-title">Popular Movies</h1>
      <ul className="movie-list" ref={movieListRef}>
        {Array.isArray(movies) && movies.length > 0 ? (
          movies.map((movie, index) => (
            <li
              key={movie.id}
              className="movie-item"
              onClick={() => handleScroll(index)} // 클릭 시 스크롤 이벤트 추가
            >
              <Link to={`/movie/${movie.id}`} className="movie-link">
                {movie.poster_path && (
                  <img
                    src={`https://image.tmdb.org/t/p/w500${movie.poster_path}`}
                    alt={movie.title}
                    className="movie-poster"
                  />
                )}
                <h2 className="movie-title">{movie.title}</h2>
              </Link>
            </li>
          ))
        ) : (
          <li>No movies available</li>
        )}
      </ul>
    </div>
  );
};

export default MovieList;
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

.movie-list {
  list-style-type: none;
  padding: 0;
  display: flex;
  overflow-x: hidden;
  scroll-snap-type: x mandatory;
  gap: 10px;
}

.movie-item {
  margin: 10px;
  text-align: center;
  scroll-snap-align: start;
  cursor: pointer;
}

.movie-link {
  text-decoration: none;
  color: #000;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.movie-poster {
  width: 150px;
  height: auto;
  border-radius: 8px;
}

.movie-title {
  margin-top: 5px;
  font-size: 1.1em;
  text-align: center;
}

4. 상세페이지 구현하기

이제 상세페이지를 위한 새로운 컴포넌트를 생성해줍니다. 이 컴포넌트에는 선택한 영화의 ID를 기반으로 API를 호출하여 상세 정보를 가져옵니다. 컴포넌트 폴더내 MovieDetail 폴더를 생성하고 그 안에 [id] 폴더를 생성해줍니다.

src/
└── components/
    ├── MovieList.js
    └── MovieDetail/
        └── [id]/
            ├── index.js
            └── MovieDetail.css
// src/App.js
import React from "react";
import MovieList from "./components/MovieList";
import { Routes, Route } from "react-router-dom";
import MovieDetail from "./components/MovieDetail/[id]";

const App = () => {
  return (
    <Routes>
      <Route path="/" element={<MovieList />} />
      <Route path="/movie/:id" element={<MovieDetail />} />
    </Routes>
  );
};

export default App;

// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { BrowserRouter } from "react-router-dom";
import { Provider } from "react-redux";
import store from "./store";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <Provider store={store}>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </Provider>,
  document.getElementById("root")
);

 
reportWebVitals();

[id]/index.js 파일에 영화 상세 페이지 컴포넌트를 구현해주고 css 스타일링을 해줍니다. 그리고 다시 app.js Route를 구현해줍니다.

// [id]/index.js
import React, { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import "./MovieDetail.css";

const MovieDetail = () => {
  const { id } = useParams(); // URL에서 영화 ID를 가져오기
  const [movie, setMovie] = useState(null); // 초기값 null
  const [loading, setLoading] = useState(true);
  const apiKey = "";

  useEffect(() => {
    const fetchMovieDetail = async () => {
      setLoading(true);
      const response = await fetch(
        `https://api.themoviedb.org/3/movie/${id}?api_key=${apiKey}`
      );
      const data = await response.json();
      setMovie(data);
      setLoading(false);
    };

    fetchMovieDetail();
  }, [id, apiKey]);

  if (loading) {
    return <div>로딩 중...</div>;
  }

  if (!movie) {
    return <div>정보 없음</div>;
  }

  return (
    <div class="movie-detail">
      <h1>{movie.title}</h1>
      <img
        src={`https://image.tmdb.org/t/p/w500${movie.poster_path}`}
        alt={movie.title}
      />
      <p>{movie.overview}</p>
      <p>Release Data: {movie.release_date}</p>
      <p>Rating: {movie.vote_average}</p>
      <div class="genres">
        <p>Genres: {movie.genres.map((genre) => genre.name).join(", ")}</p>
      </div>
    </div>
  );
};


export default MovieDetail;
// src/components/MovieDetail/[id]/MovieDetail.css
body {
  background-color: #e0f7fa;
  margin: 0;
  font-family: Arial, Helvetica, sans-serif;
}

.movie-detail {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: 20px;
  background-color: #f4f4f4;
  height: 100%;
  width: 100%;
  max-width: 600px;
  margin: 0 auto;
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
  border-radius: 10px;
}

.movie-detail img {
  width: 100%;
  max-width: 300px;
  border-radius: 8px;
  margin-bottom: 15px;
}

.movie-detail h1 {
  font-size: 2em;
  margin: 20px 0;
  color: #333;
}

.movie-detail p {
  font-size: 1.2em;
  line-height: 1.5;
  color: #555;
  margin: 5px 0;
}

.movie-detail .genres {
  margin: 10px 0;
  font-size: 1em;
  color: #777;
}

5. 트레일러 구현

TMDB API에서 영화에 대한 트레일러 정보를 가져오기 위해 API를 호출합니다. 

// src/components/MovieDetail/[id]/index.js
import React, { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import "./MovieDetail.css";

const MovieDetail = () => {
  const { id } = useParams(); // URL에서 영화 ID를 가져오기
  const [movie, setMovie] = useState(null); // 초기값 null
  const [trailer, setTrailer] = useState(null);
  const [loading, setLoading] = useState(true);
  const apiKey = process.env.REACT_APP_TMDB_API_KEY;

  useEffect(() => {
    const fetchMovieDetail = async () => {
      setLoading(true);
      const response = await fetch(
        `https://api.themoviedb.org/3/movie/${id}?api_key=${apiKey}`
      );
      const data = await response.json();
      setMovie(data);

      // 트레일러 정보 가져오기
      const videoResponse = await fetch(
        `https://api.themoviedb.org/3/movie/${id}/videos?api_key=${apiKey}`
      );
      const videoData = await videoResponse.json();
      const trailerVideo = videoData.results.find(
        (video) => video.type === "Trailer"
      );
      setTrailer(trailerVideo);
      setLoading(false);
    };

    fetchMovieDetail();
  }, [id, apiKey]);

  if (loading) {
    return <div>로딩 중...</div>;
  }

  if (!movie) {
    return <div>정보 없음</div>;
  }

  return (
    <div class="movie-detail">
      <h1>{movie.title}</h1>
      <img
        src={`https://image.tmdb.org/t/p/w500${movie.poster_path}`}
        alt={movie.title}
      />
      <p>{movie.overview}</p>
      <p>Release Data: {movie.release_date}</p>
      <p>Rating: {movie.vote_average}</p>
      <div class="genres">
        <p>Genres: {movie.genres.map((genre) => genre.name).join(", ")}</p>
      </div>

      {/* 트레일러 구현 */}
      {trailer && (
        <div class="trailer">
          <h2>Watch the Trailer</h2>
          <iframe
            width="100%"
            height="315"
            src={`https://www.youtube.com/embed/${trailer.key}`}
            title="YouTube video player"
          ></iframe>
        </div>
      )}
      <a href="#" className="button">
        Watch Trailer
      </a>
    </div>
  );
};

export default MovieDetail;
// src/components/MovieDetail/[id]/MovieDetail.css
.trailer {
  margin: 20px 0;
  width: 100%;
  max-width: 600px;
}

.trailer h2 {
  margin-bottom: 10px;
  color: #333;
}

.movie-detail iframe {
  border-radius: 8px;
}

 

 

GitHub - Koras02/react-TMDB-Bloging

Contribute to Koras02/react-TMDB-Bloging development by creating an account on GitHub.

github.com

 

728x90
LIST

'Front-End > ReactJS' 카테고리의 다른 글

[ReactJS] React + MySQL 게시판 만들기  (0) 2025.03.12
[React] React-Readux 사용법  (0) 2025.03.03
[ReactJS] 8장 Typescript  (0) 2025.02.27
[ReactJS] 7장 상태관리  (0) 2025.02.26
[ReactJS] 6장 폼  (0) 2025.02.25