Project using React/Cloning Netflix
React - Create MoviePresenter
Cog Factory
2021. 5. 9. 10:47
Movie/MoviePresenter.js
import Helmet from "react-helmet";
import styled from "styled-components";
import PropTypes from "prop-types";
import Loader from "Components/Loader";
import Poster from "Components/Poster";
import Section from "Components/Section";
const Container = styled.div`
padding: 10px;
`;
const MoviePresenter = ({ nowPlaying, upcoming, popular, error, loading }) => (
<>
<Helmet>
<title>Movies | Nomflix</title>
</Helmet>
{loading ? (
<Loader />
) : (
<Container>
<Section title="Now Playing">
{nowPlaying.map((movie) => (
<Poster
key={movie.id}
id={movie.id}
imageUrl={movie.poster_path}
title={movie.original_title}
rating={movie.vote_average}
year={movie.release_date.substring(0, 4)}
isMovie={true}
/>
))}
</Section>
<Section title="Upcoming">
{upcoming.map((movie) => (
<Poster
key={movie.id}
id={movie.id}
imageUrl={movie.poster_path}
title={movie.original_title}
rating={movie.vote_average}
year={movie.release_date.substring(0, 4)}
isMovie={true}
/>
))}
</Section>
<Section title="Popular">
{popular.map((movie) => (
<Poster
key={movie.id}
id={movie.id}
imageUrl={movie.poster_path}
title={movie.original_title}
rating={movie.vote_average}
year={movie.release_date.substring(0, 4)}
isMovie={true}
/>
))}
</Section>
</Container>
)}
</>
);
MoviePresenter.propTypes = {
nowPlaying: PropTypes.array,
popular: PropTypes.array,
upcoming: PropTypes.array,
loading: PropTypes.bool.isRequired,
error: PropTypes.string,
};
export default MoviePresenter;
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는 필요가 없다.
Components/Section.js
import PropTypes from "prop-types";
import styled from "styled-components";
const Container = styled.section`
:not(:last-child) {
margin-bottom: 50px;
}
`;
const Title = styled.h2`
font-size: 14px;
font-weight: 600;
`;
const Grid = styled.div`
margin-top: 25px;
display: grid;
grid-template-columns: repeat(auto-fill, 125px);
grid-gap: 25px;
`;
const Section = ({ title, children }) => (
<Container>
<Title>{title}</Title>
<Grid>{children}</Grid>
</Container>
);
Section.propTypes = {
title: PropTypes.string.isRequired,
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
]),
};
export default Section;
children
MoviePresenter가 <Section>에게 준 prop은 title 뿐이다. 하지만 Section에는 children이 정의되어 있다. children은 open component tag와 close component tag 사이에 있는 아이다. MoviePresenter를 보면 <Section></Section> 사이에 <Poster> list가 있다. 이것이 Section에게 children이라는 특별한 prop으로 전달된다.
Components/Poster.js
import styled from "styled-components";
import PropTypes from "prop-types";
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 }) => (
<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>
);
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;
참고 자료
- 노마드 코더의 React 멤버쉽 강의
- prop-types
- React propTypes 설명
- Children prop
소스 코드
github.com/zpskek/Nomflix-v2/commit/dc55c54f3061bfabf05b2bf2949db4064551612b