관리 메뉴

사과하는 제라스

#4. MAKING THE MOVIE APP 본문

ReactJS 영화 웹 서비스 만들기

#4. MAKING THE MOVIE APP

Xerath(제라스) 2021. 11. 2. 00:24

목차

    728x90
    반응형

    1) JavaScript에서는 주로 data를 fetch하기 위해서 fetch 함수를 사용했지만 React에서는 더욱 좋은 함수인 axios를 사용하면 좋다. axios는 일종의 fetch 위에 있는 작은 layer이다.

    일단 axios를 사용하기 위해서 axios를 설치하자.

    npm i axios

     

    2) axios를 통해서 yts라는 영화 페이지에서 영화에 대한 데이터들을 가져올 것임. yts라는 사이트에서 맨밑에 있는 API라는 란에서 movie list에 대한 주소를 긁어오면 되는데 이 사이트가 URL이 자주 바뀜(불법 다운로드 사이트인가 봄.) 그래서 이러한 변화를 적용해주는 방식으로 만들어둔 주소인 다음 URL을 사용할 것임.

    https://yts.mx/api/v2/list_movies.json

    getMovies라는 함수를 통해 movies라는 변수에 데이터를 받아오고 이때 시간이 걸리므로 async - await를 통해 기다리는 시간을 갖는 기능을 구현하고 이 함수를 componentDidMount 함수에서 실행함.

     

    <App.js>

    import axios from 'axios';
    class App extends React.Component{
    
      state = {
        isLoading: true
      }
    
      getMovies = async () => {
        const movies = await axios.get('https://yts-proxy.now.sh/list_movies.json');
    
      }
      componentDidMount(){
        this.getMovies();
      }

    3) movies를 받아오면 data 안의 data 안에 movies라는 카테고리 안에 id, year, title 등등의 데이터가 담겨있는 것을 알 수 있다. 이를 활용하기 위해서 다음과 같이 구현하여 바로 사용할 수 있게 한다. (이때, rating 점수의 크기순서대로 작성하기 위해서 URL 뒤에 sort_by=rating을 넣어준다.)

    const {data:{data:{movies}}} = await axios.get('https://yts-proxy.now.sh/list_movies.json?sort_by=rating');

    그 다음 줄에 다음과 같이 작성하여 movies에서 가져온 데이터를 movies_list라는 state element에 담아주고 isLoading 상태는 false로 바꾼다.

      getMovies = async () => {
        const {data:{data:{movies}}} = await axios.get('https://yts-proxy.now.sh/list_movies.json?sort_by=rating');
        this.setState({movies_list:movies, isLoading :false});
      }

    4) Movies.js를 만들자. propTypes로 element검사를 실시하고 Movie 컴포넌트로 받아온 id, year, title, summary, poster 변수에 대해서 출력할 것들을 return 문에 넣어주도록 하자. (이때 항상 export default 해주는 것을 깜빡하지 말도록 하자.) 아래와 같이 작성하면 title만 출력이 된다.

    <src/Movies.js>

    import React from 'react';
    import PropTypes from 'prop-types';
    
    function Movie({id, year, title, summary, poster}){
        return <h1>{title}</h1>
    }
    //state가 필요없다면 class 컴포넌트로 만들지 않고 function형태로 만들어주어도 된다.
    
    Movie.propTypes = {
        id:PropTypes.number.isRequired,
        year:PropTypes.number.isRequired,    
        title:PropTypes.string.isRequired,
        summary:PropTypes.string.isRequired,
        poster:PropTypes.string.isRequired
    };
    
    export default Movie;

    5) 이후 movies_list에 담긴 영화 데이터들을 movie라는 변수를 통해 map함수로 하나씩 Movie 컴포넌트에 props들을 전달하여 실행하도록 한다. (이때 poster에 대한 movies_list의 데이터명은 medium_cover_image인 것을 실수하지 말자!)

    <App.js>

     render(){
        const { isLoading, movies_list } = this.state;
        return(
          <div>
            {isLoading? "Loading" : 
              movies_list.map(movie => {
                console.log(movie); 
                return <Movie 
                  key = {movie.id}
                  id = {movie.id} 
                  year = {movie.year}
                  title = {movie.title}
                  summary = {movie.summary}
                  poster = {movie.medium_cover_image}
                  />;
                })}
          </div>
        )
      }

    6) CSS로 스타일 꾸미기에 영향을 주기 위하여 다음과 같이 App.js의 render함수를 HTML형태로 바꿔주자. 

    <App.js>

      render(){
        const { isLoading, movies_list } = this.state;
        return (
          <section class="container">
            {isLoading
            ? 
            (<div class="loader">
              <span class="loader__text">Loading...</span> 
            </div>)
            :
            <div class="movies">
              {movies_list.map(movie => (
                <Movie 
                  key = {movie.id}
                  id = {movie.id} 
                  year = {movie.year}
                  title = {movie.title}
                  summary = {movie.summary}
                  poster = {movie.medium_cover_image}
                  />
                ))}
            </div>
            }
          </section>
        )
      }

    Movie.js도 다음과 같이 HTML을 적용하여 변경해주자.(Movie에 전달해줄 때 id는 사용하지 않으므로 props로 보내주지는 않았다.)

    function Movie({year, title, summary, poster}){
        return (
            <div class="movie">
                <img src={poster} alt={title} title={title}/>
                <div class="movie_data">
                    <h3 class="movie__title">{title}</h3>
                    <h5 class="movie__year">{year}</h5>
                    <p class="movie__summary">{summary}</p>
                </div>
            </div>
        )
    }

    7) style component를 활용해서 변화를 주자. 원래는 아래와 같이 style 옵션을 주어서 javascript에 css를 적용할 수가 있다. 하지만 이를 일일이 넣기보다는 CSS 컴포넌트를 하나 생성해서 그걸 이용해서 CSS를 적용할 수 있다.

                    <h5 class="movie__year" style ={{backgroundColor: "red"}}>{year}</h5>

    src파일에 App.css와 Movie.css 파일을 만든 후 App.js와 Movie.js에서 각각의 CSS 파일을 import 해주자.

    <App.js>

    import './App.css';

    <Movie.css>

    import './Movie.css';

    8) 한번 장르에 대한 내용을 가져와보자. 장르는 genres라는 이름의 요소로 전달되어지고 이는 array형 배열이다. 먼저, App.js에서 props요소로 genres를 추가하고 Movie.js에서 받아온 props 목록에 추가하고 map 함수를 이용하여 genre를 목록 형식으로 나열한다. (이때 map함수는 key를 항상 포함해야 하므로 map의 기본 element인 index를 key로 대신 놓고 작성하자.)

     

    <App.js>

      render(){
        const { isLoading, movies_list } = this.state;
        return (
          <section className="container">
            {isLoading
            ? 
            (<div className="loader">
              <span className="loader__text">Loading...</span> 
            </div>)
            :
            <div className="movies">
              {movies_list.map(movie => (
                <Movie 
                  key = {movie.id}
                  id = {movie.id} 
                  year = {movie.year}
                  title = {movie.title}
                  summary = {movie.summary}
                  poster = {movie.medium_cover_image}
                  genres = {movie.genres}
                  />
                ))}
            </div>
            }
          </section>
        )
      }

    <Movie.js>

    function Movie({year, title, summary, poster, genres}){
        return (
            <div className="movie">
                <img src={poster} alt={title} title={title}/>
                <div className="movie_data">
                    <h3 className="movie__title">{title}</h3>
                    <h5 className="movie__year" style ={{backgroundColor: "red"}}>{year}</h5>
                    <p className="movie__summary">{summary}</p>
                    <ul className="genres">{genres.map( (genre, index) => <li key= {index} className="genres__genre">{genre}</li>)}</ul>
                </div>
            </div>
        )
    }
    //state가 필요없다면 class 컴포넌트로 만들지 않고 function형태로 만들어주어도 된다.
    
    Movie.propTypes = {
        id:PropTypes.number.isRequired,
        year:PropTypes.number.isRequired,    
        title:PropTypes.string.isRequired,
        summary:PropTypes.string.isRequired,
        poster:PropTypes.string.isRequired,
        genres:PropTypes.arrayOf(PropTypes.string).isRequired
    };

    9) 이제 적용할 CSS인 App.css와 Movie.css를 다음과 같이 만들어주자.

    <App.css>

    body {
        margin: 0;
        padding: 0;
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
          Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
        background-color: #eff3f7;
        height: 100%;
    }
    
    html,
    body,
    #potato,
    .container {
      height: 100%;
      display: flex;
      justify-content: center;
    }
    
    .loader {
      width: 100%;
      height: 100%;
      display: flex;
      justify-content: center;
      align-items: center;
      font-weight: 300;
    }
    
    .movies {
      display: flex;
      justify-content: space-between;
      align-items: flex-start;
      flex-wrap: wrap;
      padding: 50px;
      padding-top: 70px;
      width: 80%;
    }
    
    .movies .movie {
      width: 45%;
      background-color: white;
      margin-bottom: 70px;
      display: flex;
      align-items: flex-start;
      justify-content: space-between;
      font-weight: 300;
      padding: 20px;
      border-radius: 5px;
      color: #adaeb9;
      box-shadow: 0 13px 27px -5px rgba(50, 50, 93, 0.25),
        0 8px 16px -8px rgba(0, 0, 0, 0.3), 0 -6px 16px -6px rgba(0, 0, 0, 0.025);
    }
    
    .movie img {
      position: relative;
      top: -50px;
      max-width: 150px;
      width: 100%;
      margin-right: 30px;
      box-shadow: 0 30px 60px -12px rgba(50, 50, 93, 0.25),
        0 18px 36px -18px rgba(0, 0, 0, 0.3), 0 -12px 36px -8px rgba(0, 0, 0, 0.025);
    }
    
    .movie .movie__title,
    .movie .movie__year {
      margin: 0;
      font-weight: 300;
    }
    
    .movie .movie__title {
      margin-bottom: 5px;
      font-size: 24px;
      color: #2c2c2c;
    }
    
    .movie .movie__genres {
      list-style: none;
      padding: 0;
      margin: 0;
      display: flex;
      margin: 5px 0px;
    }
    
    .movie__genres li,
    .movie .movie__year {
      margin-right: 10px;
      font-size: 14px;
    }

    <Movie.css>

    .movies .movie {
        background-color: white;
        margin-bottom: 70px;
        font-weight: 300;
        padding: 20px;
        border-radius: 5px;
        color: #adaeb9;
        box-shadow: 0 13px 27px -5px rgba(50, 50, 93, 0.25),
          0 8px 16px -8px rgba(0, 0, 0, 0.3), 0 -6px 16px -6px rgba(0, 0, 0, 0.025);
      }
      
      .movies .movie a {
        display: grid;
        grid-template-columns: minmax(150px, 1fr) 2fr;
        grid-gap: 20px;
        text-decoration: none;
        color: inherit;
      }
      
      .movie img {
        position: relative;
        top: -50px;
        max-width: 150px;
        width: 100%;
        margin-right: 30px;
        box-shadow: 0 30px 60px -12px rgba(50, 50, 93, 0.25),
          0 18px 36px -18px rgba(0, 0, 0, 0.3), 0 -12px 36px -8px rgba(0, 0, 0, 0.025);
      }
      
      .movie .movie__title,
      .movie .movie__year {
        margin: 0;
        font-weight: 300;
      }
      
      .movie .movie__title {
        margin-bottom: 5px;
        font-size: 24px;
        color: #2c2c2c;
      }
      
      .movie .movie__genres {
        list-style: none;
        padding: 0;
        margin: 0;
        display: flex;
        flex-wrap: wrap;
        margin: 5px 0px;
      }
      
      .movie__genres li,
      .movie .movie__year {
        margin-right: 10px;
        font-size: 14px;
      }

    10) 이렇게 만들면 css가 적용되었을 때 summary 길이가 다르므로 각 영화들마다의 정보 길이가 달라 줄 길이가 다른데 이를 summary.slice 기능을 사용하여 잘라주자.

    <Movie.js>

                    <p className="movie__summary">{summary.slice(0, 140)}...</p>

     

    728x90
    반응형

    'ReactJS 영화 웹 서비스 만들기' 카테고리의 다른 글

    #6. ROUTING  (0) 2021.11.14
    #5. CONCLUSIONS  (0) 2021.11.02
    #3. STATE  (0) 2021.10.26
    #2. JSX & PROPS  (0) 2021.10.17
    #1. SETUP  (0) 2021.10.15