-
Components/Poster.js
import styled from "styled-components"; import PropTypes from "prop-types"; import { Link } from "react-router-dom"; const Container = styled.div``; const Image = styled.div` background-image: url(${(props) => props.bgUrl}); width: 100%; height: 180px; background-repeat: no-repeat; background-size: cover; background-position: center; transition: opacity 0.1s linear; `; const Rating = styled.span` bottom: 5px; right: 5px; position: absolute; opacity: 0; transition: opacity 0.1s linear; `; const ImageContainer = styled.div` position: relative; &:hover { ${Image} { opacity: 0.3; } ${Rating} { opacity: 1; } } `; const Title = styled.span` display: block; margin-bottom: 3px; `; const Year = styled.span` font-size: 10px; color: rgba(255, 255, 255, 0.5); `; const Poster = ({ id, imageUrl, title, rating, year, isMovie = false }) => ( <Link to={isMovie ? `/movie/${id}` : `/tv/${id}`}> <Container> <ImageContainer> <Image bgUrl={ imageUrl ? `https://image.tmdb.org/t/p/w300${imageUrl}` : require("../assets/noPosterSmall.png") } /> <Rating> <span role="img" aria-label="rating"> ⭐️ </span>{" "} {rating}/10 </Rating> </ImageContainer> <Title> {title.length > 18 ? `${title.substring(0, 18)}...` : title} </Title> <Year>{year}</Year> </Container> </Link> ); Poster.propTypes = { id: PropTypes.number.isRequired, imageUrl: PropTypes.string, title: PropTypes.string.isRequired, rating: PropTypes.number, year: PropTypes.string, isMovie: PropTypes.bool, }; export default Poster;
Poster에 Link를 추가했다. 이제 해당 링크로 Detail page를 갈 수 있다.
Detail/DetailContainer.js
import React from "react"; import DetailPresenter from "./DetailPresenter"; import { moviesApi, tvApi } from "api"; export default class extends React.Component { constructor(props) { super(props); const { location: { pathname }, } = props; this.state = { result: null, loading: true, error: null, isMovie: pathname.includes("/movie/"), }; } async componentDidMount() { const { match: { params: { id }, }, } = this.props; let result = null; const { isMovie } = this.state; try { if (isMovie) { ({ data: result } = await moviesApi.movieDetail(id)); } else { ({ data: result } = await tvApi.showDetail(id)); } this.setState({ result, }); } catch { this.setState({ error: "Can't find anything.", }); } finally { this.setState({ loading: false, }); } } render() { const { result, error, loading } = this.state; return <DetailPresenter result={result} error={error} loading={loading} />; } }
ComponentDidMount()를 사용하면 this.props를 사용할 수 있다. prop에는 <Route>가 넘겨주는 location과 params이 있다. isMovie는 url을 읽고 해당 Poster가 Movie Poster인지 TV Poster인지 확인한다.
Detail/DetailPresenter.js
import React from "react"; import PropTypes from "prop-types"; import styled from "styled-components"; import Helmet from "react-helmet"; import Loader from "Components/Loader"; const Container = styled.div` height: calc(100vh - 50px); width: 100%; position: relative; padding: 50px; `; const Backdrop = styled.div` position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-image: url(${(props) => props.bgImage}); background-position: center center; background-size: cover; filter: blur(3px); opacity: 0.5; z-index: 0; `; const Content = styled.div` display: flex; width: 100%; position: relative; z-index: 1; height: 100%; `; const Cover = styled.div` width: 30%; background-image: url(${(props) => props.bgImage}); background-position: center center; background-size: cover; height: 100%; border-radius: 5px; `; const Data = styled.div` width: 70%; margin-left: 10px; `; const Title = styled.h3` font-size: 32px; `; const ItemContainer = styled.div` margin: 20px 0; `; const Item = styled.span``; const Divider = styled.span` margin: 0 10px; `; const Overview = styled.p` font-size: 12px; opacity: 0.7; line-height: 1.5; width: 50%; `; const DetailPresenter = ({ result, loading, error }) => loading ? ( <> <Helmet> <title>Loading | Nomflix</title> </Helmet> <Loader /> </> ) : ( <Container> <Helmet> <title> {result.original_title ? result.original_title : result.original_name}{" "} | Nomflix </title> </Helmet> <Backdrop bgImage={`https://image.tmdb.org/t/p/original${result.backdrop_path}`} /> <Content> <Cover bgImage={ result.poster_path ? `https://image.tmdb.org/t/p/original${result.poster_path}` : require("../../assets/noPosterSmall.png") } /> <Data> <Title> {result.original_title ? result.original_title : result.original_name} </Title> <ItemContainer> <Item> {result.release_date ? result.release_date.substring(0, 4) : result.first_air_date.substring(0, 4)} </Item> <Divider>•</Divider> <Item> {result.runtime ? result.runtime : result.episode_run_time[0]} min </Item> <Divider>•</Divider> <Item> {result.genres && result.genres.map((genre, index) => index === result.genres.length - 1 ? genre.name : `${genre.name} / ` )} </Item> </ItemContainer> <Overview>{result.overview}</Overview> </Data> </Content> </Container> ); DetailPresenter.propTypes = { result: PropTypes.object, loading: PropTypes.bool.isRequired, error: PropTypes.string, }; export default DetailPresenter;
key
nowPlaying, upcoming, popular는 모두 배열이다. 이들을 map을 이용해서 <Poster>를 부르고 있다. 이 때 리스트 안에 있는 <Poster>에 key 값을 추가해야만 한다.
prop-types
설치
$ npm i prop-types
개요
prop-types는 prop의 type을 정의한다. prop을 받을 component 뒤에 method 형식으로 propTypes를 하고 객체를 정의한다.
위 코드와 같이 type을 정의하면 MoviePresenter가 받아들이는 prop이 옳지 않은 type일 때 error를 일으켜 개발자가 실수 하는 것을 막아준다. 만약 React를 typescript로 짰다면 prop-types는 필요가 없다.
참고 자료
- 노마드 코더의 React 멤버쉽 강의
- prop-types
- React propTypes 설명
소스 코드
github.com/zpskek/Nomflix-v2/commit/63c01c5343fe6abf64e1b0ab9590bd33d60f58d9
'Project using React > Cloning Netflix' 카테고리의 다른 글
Netlify에 React 배포 (0) 2021.05.09 React - Search Container and Presenter (0) 2021.05.09 React - Create TV Container and Presenter (0) 2021.05.09 React - Create MoviePresenter (0) 2021.05.09 React - Create Loader.js (0) 2021.05.09