🔐 Archive/ReactJS

ReactJS로 웹서비스 만들기 4: Movie app 만들기

YoungRock 2020. 3. 6. 17:01

[1] API에서 데이터 가져오기 (fetch)

 

  • 설치: npm i axios (i는 install의 약자)

 

API Documentation - YTS YIFY

Official YTS YIFY API documentation. YTS offers free API - an easy way to access the YIFY movies details.

yts.mx

그 중 List Movies를 사용하려 한다.

첫번째 Endpoint를 확인해보면 이와같이 json으로 된 데이터들을 확인할 수 있다.

TIP! 크롬 확장 프로그램 JSON Viewer를 설치하면 이렇게 깔끔하게 볼수있음!

하지만 이 url을 사용하면 url이 계속 바뀌기 때문에 프로그램을 만들어도 항상 에러가 생긴다!

그래서 노마드 코드에서 제공하는 url을 사용하기로 한다. (https://github.com/serranoarevalo/yts-proxy)

-> https://yts-proxy.now.sh/list_movies.json 


import React from 'react';
import axios from 'axios';

class App extends React.Component {
  state = {
    isLoading: true,
    movie: []
  };

  componentDidMount(){
    axios.get("https://yts-proxy.now.sh/list_movies.json");
  }

  render() {
    const { isLoading } = this.state;
  return <div>{isLoading ? "Loading" : "We are ready"}</div>;
  }
}

export default App;

하지만 이렇게 하면 api에서 데이터를 사용하지 않고 있기 때문에 에러가 남 -> 변수에 담음

import React from 'react';
import axios from 'axios';

class App extends React.Component {
  state = {
    isLoading: true,
    movie: []
  };

  getMovies = async() => {
    const movies = await axios.get("https://yts-proxy.now.sh/list_movies.json");
  }

  componentDidMount(){
    this.getMovies();
  }

  render() {
    const { isLoading } = this.state;
  return <div>{isLoading ? "Loading" : "We are ready"}</div>;
  }
}

export default App;

하지만 movies변수에 담았지만, 이때 데이터를 가져오는데 시간이 조금 걸릴 수 있다. 그렇기 때문에 데이터를 가져올 때까지 기다려야함 => await를 이용 => await를 쓰려면 그 함수가 async해야하므로 async함수로 만듦


[2] movie 데이터 출력 (rendering)

  getMovies = async() => {
    const movies = await axios.get("https://yts-proxy.now.sh/list_movies.json");
    console.log(movies);
  }

movies를 출력해보면 다음과 같다

우리가 원하는 데이터는 object>data>data>movies 이다.
즉, console.log(movies.data.data.movies); 를 하면 우리가 원하는 movies 데이터만 콘솔창에 출력된다.

하지만 이렇게 표현하면 movies변수에 movies 데이터만 담을 수 있다.

  getMovies = async () => {
    const { data: { data: { movies } } } = await axios.get("https://yts-proxy.now.sh/list_movies.json");
    console.log(movies);
  }

그리고 이 변수를 state movie변수에 지정해주어야 하는데

this.setState({movies: movies}) // 원래는 이렇게 해야하지만
this.setState({movies}) // 리액트는 똑똑해서 이렇게 해도 알아듣는다

 

// App.js
import React from 'react';
import axios from 'axios';

class App extends React.Component {
  state = {
    isLoading: true,
    movie: []
  };

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

  componentDidMount(){
    this.getMovies();
  }

  render() {
    const { isLoading } = this.state;
    return <div>{isLoading ? "Loading" : "We are ready"}</div>;
  }
}

export default App;

movies 데이터를 가져왔으면 isLoading 변수도 false로 바꿔준다.

이 때, 두개의 state를 바꾸지만 한번의 setState만 사용하면 된다.


  • Movie.js 파일 생성

api를 확인해보면 endpoint parpameter에 sort_by가 있고, 평점(rating)으로도 정렬을 할 수 있는것을 확인할 수있다.

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

따라서 url에 sort_by를 추가해주었다. (App.js 파일)

// App.js
import React from "react";
import axios from "axios";
import Movie from "./Movie";

class App extends React.Component {
  state = {
    isLoading: true,
    movie: []
  };

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

  componentDidMount(){
    this.getMovies();
  }

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

export default App;

이때, key값을 지정하지 않으면 다음과 같은 에러가 뜬다.

// Movie.js
import React from "react";
import PropTypes from "prop-types";

function Movie({id, year, title, summary, poster}){
    return <h4>{title}</h4>;
}

Movie.propTypes = {
    id: PropTypes.number.isRequired,
    year: PropTypes.number.isRequired,
    title: PropTypes.string.isRequired,
    summary: PropTypes.string.isRequired,
    poster: PropTypes.string.isRequired //medium_cover_image
}

export default Movie;

 

[결과]

데이터가 모두 로딩되면 title을 출력


[3] Styling the Movies

// App.js
import React from "react";
import axios from "axios";
import Movie from "./Movie";

class App extends React.Component {
  state = {
    isLoading: true,
    movie: []
  };

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

  componentDidMount(){
    this.getMovies();
  }

  render() {
    const { isLoading, movies } = this.state;
    return (<section class="container">
      {isLoading ? (
        <div class="loader">
          <span class="loader_text">Loading...</span>
        </div>
      ) : (
        <div class="movies">
          {movies.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>);
  }
}

export default App;

 

// Movie.js
import React from "react";
import PropTypes from "prop-types";
import "./Movie.css";	// 스타일 추가

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>
    );
}

Movie.propTypes = {
    id: PropTypes.number.isRequired,
    year: PropTypes.number.isRequired,
    title: PropTypes.string.isRequired,
    summary: PropTypes.string.isRequired,
    poster: PropTypes.string.isRequired //medium_cover_image
}

export default Movie;

css파일을 추가해서 디자인 할 수 있다.

body {
    background-color: #f2f2f2;
}

-> Movie.css 파일

[결과]

 

[Error Fix]

위 에러가 나는 이유는 class안에서 html의 class속성을 지정했기 때문에 자바스크립트가 혼동이 왔기 때문이다.

따라서 모든 class를 className으로 바꿔주어야 한다. 비슷한에로 <label>태그의 for도 htmlFor이라고 써야한다.

import React from "react";
import axios from "axios";
import Movie from "./Movie";

class App extends React.Component {
  state = {
    isLoading: true,
    movie: []
  };

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

  componentDidMount(){
    this.getMovies();
  }

  render() {
    const { isLoading, movies } = this.state;
    return (<section className="container">
      {isLoading ? (
        <div className="loader">
          <span className="loader_text">Loading...</span>
        </div>
      ) : (
        <div className="movies">
          {movies.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>);
  }
}

export default App;

이렇게


[4] genre 추가

[Movie.js]

import React from "react";
import PropTypes from "prop-types";
import "./Movie.css";

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">{year}</h5>
                <ul className="movie__genres">
                    {genres.map((genre, index) => (
                        <li key={index} className="genres__genre">{genre}</li>
                    ))}
                </ul>
                <p className="movie__summary">{summary.slice(0, 180)}...</p>
            </div>
        </div>
    );
}

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
}

export default Movie;

genre는 배열

slice -> css를 적용해서 출력할 때 summary는 0-180까지만 출력하고 이후는 ...으로 생략하기 위해 사용

[App.js]

import React from "react";
import axios from "axios";
import Movie from "./Movie";

class App extends React.Component {
  state = {
    isLoading: true,
    movie: []
  };

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

  componentDidMount(){
    this.getMovies();
  }

  render() {
    const { isLoading, movies } = this.state;
    return (<section className="container">
      {isLoading ? (
        <div className="loader">
          <span className="loader_text">Loading...</span>
        </div>
      ) : (
        <div className="movies">
          {movies.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>);
  }
}

export default App;

 

[결과]

 

[에러]

genres에 key가 없으면 발생하는 에러

<ul className="genres">
  {genres.map((genre, index) => (
  	<li key={index} className="genres__genre">{genre}</li>
  ))}
</ul>

[Movie.js] map에서 index는 0,1,2,3 와 같이 오름차순으로 붙는 숫자이다. 이것을 key로 지정


[5] css 추가