React

 

따라하며 배우는 리액트 A-Z

 


[프론트엔드, 웹 개발] 강의입니다.

이 강의를 통해 리액트 기초부터 중급까지 배우게 됩니다. 하나의 강의로 개념도 익히고 실습도 하며, 리액트를 위해 필요한 대부분의 지식을 한번에 습득할 수 있도록 만들었습니다.

✍️
이런 걸
배워요!

리액트

NextJS

타입스크립트

정적 사이트 자동 배포

도커

 

강의:  https://www.inflearn.com/course/%EB%94%B0%EB%9D%BC%ED%95%98%EB%8A%94-%EB%A6%AC%EC%95%A1%ED%8A%B8#

 

강의 자료 :  https://github.com/braverokmc79/DiagramPDF

 

소스:  https://github.dev/braverokmc79/react-netflix-clone

 

 

 

 

 

[4]. Netflix 앱 완성하기

 

 

44.영화 나열을 위한 Row 컴포넌트 생성하기

 

강의:

https://www.inflearn.com/course/%EB%94%B0%EB%9D%BC%ED%95%98%EB%8A%94-%EB%A6%AC%EC%95%A1%ED%8A%B8/unit/119886?tab=curriculum

 

 

 

 

src/components/Row.js

import React, { useEffect, useState } from 'react';
import axios from '../api/axios';
import "./Row.css";

const Row = ({ isLargeRow, title, id, fetchUrl }) => {
    const [movies, setMovies] = useState([]);

    useEffect(() => {
        fetchMovieData();
    }, []);

    const fetchMovieData = async () => {
        const request = await axios.get(fetchUrl);
        setMovies(request.data.results);
    }

    console.log("title : ", movies);


    return (
        <section className='row'>
            <h2>{title}</h2>
            <div className='slider'>
                <div className='slider_arrow-left'>
                    <span className='arrow'>{"<"}</span>
                </div>


                <div id={id} className="row_posters">
                    {
                        movies.map((movie) => {
                            return (
                                <div className='poster' key={movie.id}>
                                    <img

                                        className={`row_poster ${isLargeRow && "row_posterLarge"}`}
                                        src={`https://image.tmdb.org/t/p/original/${isLargeRow ? movie.poster_path : movie.backdrop_path}`}
                                        alt={movie.name}
                                    />
                                    <span className='movie_name'>{movie.name || movie.title}</span>
                                </div>
                            )

                        })
                    }
                </div>


                <div className='slider_arrow-right'>
                    <span className='arrow'>{">"}</span>
                </div>

            </div>
        </section>
    );



};

export default Row;

 

 

 

src/components/Row.css

.row {
  margin-left: 20px;
  color: white;
  margin-bottom: 70px;
}
h2 {
  padding-left: 20px;
}
.slider {
  position: relative;
}
.slider_arrow-left {
  background-clip: content-box;
  padding: 20px 0;
  box-sizing: border-box;
  transition: 400ms all ease-in-out;
  cursor: pointer;
  width: 80px;
  z-index: 1000;
  position: absolute;
  left: 0px;
  top: 0;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  visibility: hidden;
}
.slider_arrow-right {
  padding: 20px 0;
  background-clip: content-box;
  box-sizing: border-box;
  transition: 400ms all ease-in-out;
  cursor: pointer;
  width: 80px;
  z-index: 1000;
  position: absolute;
  right: 0px;
  top: 0;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  visibility: hidden;
}
.arrow {
  transition: 400ms all ease-in-out;
}
.arrow:hover {
  transition: 400ms all ease-in-out;
  transform: scale(1.5);
}
.slider:hover .slider_arrow-left {
  transition: 400ms all ease-in-out;
  visibility: visible;
}
.slider:hover .slider_arrow-right {
  transition: 400ms all ease-in-out;
  visibility: visible;
}
.slider_arrow-left:hover {
  background: rgba(20, 20, 20, 0.5);
  transition: 400ms all ease-in-out;
}
.slider_arrow-right:hover {
  background: rgba(20, 20, 20, 0.5);
  transition: 400ms all ease-in-out;
}

.row_posters {
  display: flex;
  overflow-y: hidden;
  overflow-x: scroll;
  padding: 20px 0 20px 20px;
  scroll-behavior: smooth;
}
.row_posters::-webkit-scrollbar {
  display: none;
}
.poster{
  height: 100%;
}
.row_poster {
  object-fit: contain;
  width: 100%;
  max-height: 144px;
  margin-right: 10px;
  transition: transform 450ms;
  border-radius: 4px;
  width: auto;
}
.row_poster:hover {
  transform: scale(1.08);
}
.row_posterLarge {
  max-height: 320px;
}
.row_posterLarge:hover {
  transform: scale(1.1);
  opacity: 1;
}

.row_arrow-left {
  position: absolute;
  top: 0;
  left: 20px;
  height: 100%;
  width: 32px;
  background: rgba(0, 0, 0, 0.2);
  display: flex;
  align-items: center;
}
.row_arrow-right {
  position: absolute;
  top: 0;
  right: 0px;
  height: 100%;
  width: 32px;
  background: rgba(0, 0, 0, 0.2);
  display: flex;
  align-items: center;
}

.movie_name{
   
    bottom: 0px;
}
@media screen and (min-width: 1200px) {
  .row_poster {
    max-height: 160px;
  }
  .row_posterLarge {
    max-height: 360px;
  }
}
@media screen and (max-width: 768px) {
  .row_poster {
    max-height: 100px;
  }
  .row_posterLarge {
    max-height: 280px;
  }
}

.swiper-pagination {
  text-align: right !important;
}

.swiper-pagination-bullet {
  background: gray !important;
  opacity: 1 !important;
}

.swiper-pagination-bullet-active {
  background: white !important;
}

.swiper-button-prev {
  color: white !important;
}

.swiper-button-next {
  color: white !important;
}

.swiper-button-next:after, .swiper-button-prev:after{
  font-size: 1.3rem !important;
  font-weight: 600 !important;
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

45.슬라이드 기능 추가하기

강의:

https://www.inflearn.com/course/%EB%94%B0%EB%9D%BC%ED%95%98%EB%8A%94-%EB%A6%AC%EC%95%A1%ED%8A%B8/unit/119887?tab=curriculum

 

 

  <span className='arrow'
                        onClick={() => {
                            document.getElementById(id).scrollLeft -= window.innerWidth - 80;
                        }}

 

 

 

 

 

 

 

 

 

 

 

46.Styled Component를 이용해서 Footer 생성하기

 

강의 :

https://www.inflearn.com/course/%EB%94%B0%EB%9D%BC%ED%95%98%EB%8A%94-%EB%A6%AC%EC%95%A1%ED%8A%B8/unit/119888?tab=curriculum

 

 

 

src/components/Footer.js

import React from "react";
import styled from "styled-components";

export default function Footer() {
    return (
        <FooterContainer>
            <FooterContent>
                <FooterLinkContainer>
                    <FooterLinkTitle>Now Movie 대한민국</FooterLinkTitle>
                    <FooterLinkContent>
                        {/* <FooterLink href="https://help.netflix.com/ko/node/412">
                            Now Movie 소개
                        </FooterLink> */}
                        <FooterLink href="https://help.netflix.com/ko">
                            Now Movies 소개
                        </FooterLink>
                        <FooterLink href="https://help.netflix.com/ko/">
                            미디어 센터
                        </FooterLink>
                        <FooterLink href="https://help.netflix.com/ko/">
                            이용 약관
                        </FooterLink>
                    </FooterLinkContent>
                    <FooterDescContainer>
                        <FooterDescRights>
                            Now Movie Rights Reserved.
                        </FooterDescRights>
                    </FooterDescContainer>
                </FooterLinkContainer>
            </FooterContent>
        </FooterContainer>
    );
}

const FooterContainer = styled.div`
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 40px 0;
  border-top: 1px solid rgb(25, 25, 25);
  width: 100%;
  position: relative;
  z-index: 100;

  @media (max-width: 769px) {
    padding: 20px 20px;
    padding-bottom: 30px;
  }
`;

const FooterContent = styled.div``;

const FooterLinkContainer = styled.div`
  width: 500px;

  @media (max-width: 768px) {
    width: 100%;
  }
`;

const FooterLinkTitle = styled.h1`
  color: gray;
  font-size: 17px;
`;

const FooterLinkContent = styled.div`
  display: flex;
  justify-content: space-bewteen;
  flex-wrap: wrap;
  margin-top: 35px;

  @media (max-width: 768px) {
    margin-top: 26px;
  }
`;

const FooterLink = styled.a`
  color: gray;
  font-size: 14px;
  width: 160px;
  margin-bottom: 21px;
  text-decoration: none;

  &:hover {
    text-decoration: underline;
  }

  @media (max-width: 768px) {
    margin-bottom: 16px;
  }
`;

const FooterDescContainer = styled.div`
  margin-top: 30px;
   @media (max-width: 768px) {
    margin-top: 20px;
  }
`;

const FooterDescRights = styled.h2`
  color: white;
  font-size: 14px;
  text-align: center;
`;

 

 

 

 

 

 

 

 

 

 

47.영화 자세히 보기 클릭 시 모달 생성하기

강의 :

https://www.inflearn.com/course/%EB%94%B0%EB%9D%BC%ED%95%98%EB%8A%94-%EB%A6%AC%EC%95%A1%ED%8A%B8/unit/119889?tab=curriculum

 

 

src/components/Row.js

import React, { useEffect, useState } from 'react';
import axios from '../api/axios';
import "./Row.css";
import MovieModal from './MovieModal/index';

const Row = ({ isLargeRow, title, id, fetchUrl }) => {
    const [movies, setMovies] = useState([]);
    const [modalOpen, setModalOpen] = useState(false);
    const [movieSelected, setMovieSelected] = useState({});


    useEffect(() => {
        fetchMovieData();
    }, []);

    const fetchMovieData = async () => {
        const request = await axios.get(fetchUrl);
        setMovies(request.data.results);
    }


    const handleClick = (movie) => {
        setModalOpen(true);
        setMovieSelected(movie);
    }


    return (
        <section className='row'>
            <h2>{title}</h2>
            <div className='slider'>
                <div className='slider_arrow-left' onClick={() => {
                    document.getElementById(id).scrollLeft -= window.innerWidth - 80;
                }}>
                    <span className='arrow' >{"<"}</span>
                </div>


                <div id={id} className="row_posters">
                    {
                        movies.map((movie) => {
                            return (
                                <div className='poster' key={movie.id} onClick={() => handleClick(movie)}>
                                    <img

                                        className={`row_poster ${isLargeRow && "row_posterLarge"}`}
                                        src={`https://image.tmdb.org/t/p/original/${isLargeRow ? movie.poster_path : movie.backdrop_path}`}
                                        alt={movie.name}
                                    />
                                    <span className='movie_name'>{movie.name || movie.title}</span>
                                </div>
                            )

                        })
                    }
                </div>


                <div className='slider_arrow-right' onClick={() => {
                    document.getElementById(id).scrollLeft += window.innerWidth - 80;
                }}>
                    <span className='arrow'  >{">"}</span>
                </div>

            </div>


            {
                modalOpen &&

                <MovieModal  {...setMovieSelected} setModalOpen={setModalOpen} />
            }


        </section >
    );



};

export default Row;

 

 

 

 

src/components/MovieModal/index.js

import React from 'react';
import './MovieModal.css';
const MovieModal = ({
    backdrop_path,
    title,
    overview,
    name,
    release_date,
    first_air_date,
    vote_average,
    setModalOpen
}) => {

    return (
        <div>

        </div>
    );
};

export default MovieModal;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

48.Movie 모달 UI 생성하기

강의 :

https://www.inflearn.com/course/%EB%94%B0%EB%9D%BC%ED%95%98%EB%8A%94-%EB%A6%AC%EC%95%A1%ED%8A%B8/unit/119890?tab=curriculum

 

 

 

 

src/components/MovieModal/index.js

import React, { useEffect, useState } from 'react';
import axios from '../../api/axios';
import axiosEn from '../../api/axiosEn';
import styled from 'styled-components';
import './MovieModal.css';

const HomeContainer = styled.div`
    width: 100%;
    height: auto;
   
`;

const Iframe = styled.iframe`
    width: 100%;
    height: 500px;
    z-index: -1;
    opacity: 0.65;
    border: none;

    &::after{
       content:"" ;
       position: absolute;
       top: 0;
       left: 0;
       width: 100%;
       height: 200;       
    }
`;

const MovieModal = ({
    isLargeRow,
    id,
    backdrop_path,
    title,
    overview,
    name,
    release_date,
    first_air_date,
    vote_average,
    setModalOpen
}) => {

    const [movieKey, setMovieKey] = useState("");

    useEffect(() => {
        if (!isLargeRow) {
            movieDetail();
        }

    }, []);

    const movieDetail = async () => {

        //특정 영화의 더 상세한 정보를 가져오기 (비디오 정보도 포함)
        const movie = await axios.get(`movie/${id}`, {
            params: { append_to_response: "videos" }
        })



        //비디오가 없다면 다음을 실행
        if (movie.videos === undefined) {
            const { data: movieDetailEn } = await axiosEn.get(`movie/${id}`, {
                params: { append_to_response: "videos" },
            });
            setMovieKey(movieDetailEn.videos.results[0].key);
        } else {
            setMovieKey(movie.videos.results[0].key)
        }
    }

    console.log("영화 movie : ", movieKey);

    return (
        <div className='presentation'>
            <div className='wrapper-modal' onClick={() => setModalOpen(false)} >
                <div className='modal'>
                    <span onClick={() => setModalOpen(false)} className="modal-close">
                        x
                    </span>


                    {movieKey && <HomeContainer>
                        <Iframe
                            src={`https://www.youtube.com/embed/${movieKey}?controls=1&autoplay=1&loop=1&mute=0&playlist=${movieKey}`}
                            title="YouTube video player"
                            frameborder="0"
                            allow="autoplay; fullscreen"
                            allowfullscreen
                        ></Iframe>
                    </HomeContainer>

                    }

                    <img
                        className='modal_poster-img'
                        src={`https://image.tmdb.org/t/p/original${backdrop_path}`}
                        alt={name}
                    />

                    <div className='modal_content'>
                        <p className='modal_details'>
                            <span className='modal_user_perc'>
                                100% for you &nbsp;
                            </span>
                            {release_date ? release_date : first_air_date}
                        </p>

                        <h2 className='modal_title'>{title ? title : name}</h2>
                        <p className='modal_overview'>평점 : {vote_average}</p>
                        <p className='modal_overview'>{overview}</p>
                    </div>


                </div>
            </div>
        </div>
    );
};

export default MovieModal;

 

 

src/components/MovieModal/MovieModal.css

import React, { useEffect, useState } from 'react';
import axios from '../../api/axios';
import axiosEn from '../../api/axiosEn';
import styled from 'styled-components';
import './MovieModal.css';

const HomeContainer = styled.div`
    width: 100%;
    height: auto;
   
`;

const Iframe = styled.iframe`
    width: 100%;
    height: 500px;
    z-index: -1;
    opacity: 0.65;
    border: none;

    &::after{
       content:"" ;
       position: absolute;
       top: 0;
       left: 0;
       width: 100%;
       height: 200;       
    }
`;

const MovieModal = ({
    isLargeRow,
    id,
    backdrop_path,
    title,
    overview,
    name,
    release_date,
    first_air_date,
    vote_average,
    setModalOpen
}) => {

    const [movieKey, setMovieKey] = useState("");

    useEffect(() => {
        if (!isLargeRow) {
            movieDetail();
        }

    }, []);

    const movieDetail = async () => {

        //특정 영화의 더 상세한 정보를 가져오기 (비디오 정보도 포함)
        const movie = await axios.get(`movie/${id}`, {
            params: { append_to_response: "videos" }
        })



        //비디오가 없다면 다음을 실행
        if (movie.videos === undefined) {
            const { data: movieDetailEn } = await axiosEn.get(`movie/${id}`, {
                params: { append_to_response: "videos" },
            });
            setMovieKey(movieDetailEn.videos.results[0].key);
        } else {
            setMovieKey(movie.videos.results[0].key)
        }
    }

    console.log("영화 movie : ", movieKey);

    return (
        <div className='presentation'>
            <div className='wrapper-modal' onClick={() => setModalOpen(false)} >
                <div className='modal'>
                    <span onClick={() => setModalOpen(false)} className="modal-close">
                        x
                    </span>


                    {movieKey && <HomeContainer>
                        <Iframe
                            src={`https://www.youtube.com/embed/${movieKey}?controls=1&autoplay=1&loop=1&mute=0&playlist=${movieKey}`}
                            title="YouTube video player"
                            frameborder="0"
                            allow="autoplay; fullscreen"
                            allowfullscreen
                        ></Iframe>
                    </HomeContainer>

                    }

                    <img
                        className='modal_poster-img'
                        src={`https://image.tmdb.org/t/p/original${backdrop_path}`}
                        alt={name}
                    />

                    <div className='modal_content'>
                        <p className='modal_details'>
                            <span className='modal_user_perc'>
                                100% for you &nbsp;
                            </span>
                            {release_date ? release_date : first_air_date}
                        </p>

                        <h2 className='modal_title'>{title ? title : name}</h2>
                        <p className='modal_overview'>평점 : {vote_average}</p>
                        <p className='modal_overview'>{overview}</p>
                    </div>


                </div>
            </div>
        </div>
    );
};

export default MovieModal;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

49.React Router Dom

 

강의:

https://www.inflearn.com/course/%EB%94%B0%EB%9D%BC%ED%95%98%EB%8A%94-%EB%A6%AC%EC%95%A1%ED%8A%B8/unit/119891?tab=curriculum

 

npm install react-router-dom 

yarn add react-router-dom

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

50.React Router Dom

 

강의:

https://www.inflearn.com/course/%EB%94%B0%EB%9D%BC%ED%95%98%EB%8A%94-%EB%A6%AC%EC%95%A1%ED%8A%B8/unit/119892?tab=curriculum

 

 

 

 

 

 

 

 

 

 

 

 

51.Netflix 앱에 React Router Dom 적용하기

 

강의: https://www.inflearn.com/course/%EB%94%B0%EB%9D%BC%ED%95%98%EB%8A%94-%EB%A6%AC%EC%95%A1%ED%8A%B8/unit/119893?tab=curriculum

 

 

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

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

 

 

 

App.js

import './App.css';
import Nav from './components/Nav';
import Footer from './components/Footer';
import { Outlet, Routes ,Route } from 'react-router-dom';
import MainPage from './pages/MainPage';
import DetailPage from './pages/DetailPage';
import SearchPage from './pages/SearchPage';


const Layout =()=>{
  return(
    <div>
       <Nav />

       <Outlet />

       <Footer />
    </div>
  )
}


function App() {
  return (
    <div className="App">
      <Routes>
        <Route path='/'  element={<Layout />}>
           <Route index element={<MainPage />} />
           <Route path=":movieId" element={<DetailPage />} />
           <Route path="search" element={<SearchPage />} />
        </Route>
      </Routes>
    </div>
  );
}

export default App;

 

 

 

src/pages/MainPage/index.js

 

import React from 'react'
import requests from '../../api/requests'
import Banner from '../../components/Banner'
import Row from '../../components/Row'

function MainPage() {
  return (
    <div>
        <Banner />

        <Row
        title="넷플릭스 오리지널"
        id="NO"
        fetchUrl={requests.fetchNetflixOriginals}
        isLargeRow
        />


        <Row title="Trending Now" id="TN" fetchUrl={requests.fetchTrending} />
        <Row title="Top Rated" id="TR" fetchUrl={requests.fetchTopRated} />
        <Row title="액션 영화" id="AM" fetchUrl={requests.fetchActionMovies} />
        <Row title="코미디 영화" id="CM" fetchUrl={requests.fetchComedyMovies} />
        <Row title="공포 영화" id="HM" fetchUrl={requests.fetchHorrorMovies} />
        <Row title="로맨스 영화" id="RM" fetchUrl={requests.fetchRomanceMovies} />
        <Row title="다큐멘터리" id="DM" fetchUrl={requests.fetchDocumentaries} />

    </div>
  )
}

export default MainPage

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

리액트

 

about author

PHRASE

Level 60  라이트

He who runs may read. (달리면서도 읽을 수 있다. = 매우 명백하다).

댓글 ( 4)

댓글 남기기

작성