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
'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 |