상세 컨텐츠

본문 제목

[React] Nomad-coder 강의 React-Movie-App 완성 코드 분석

프로그래밍/기타

by jisooo 2019. 5. 19. 21:54

본문

#nomadcoder #react #reactjs #movieapp #리액트 #javascript #자바스크립트

 

 

 

 

프로젝트 완성 및 정리 기념, React-Movie-app 코드 구조를 정리해본다!

우선 전체 코드 참조 구조는

index.js <- App.js <- Movie.js

최하위 자식 컴포넌트 Movie.js에서는

Movie, MoviePoster, MovieGenre function을 정의하였다.

MoviePoster와 MovieGenre 컴포넌트는 Movie 컴포넌트의 자식 컴포넌트이다.

따라서 Movie 컴포넌트에는 MoviePoster, MovieGenre 컴포넌트가 포함된다.

ReactDOM.render(<App />, document.getElementById('root'));

최상위 index.js에서는 최종적으로 App.js에서 작성된 컴포넌트들을 root의 html 태그에 렌더링시키는 역할이다.

index.js는 간단하므로 아래와 같이 전체 코드를 한꺼번에 올린다.

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';

ReactDOM.render(<App />, document.getElementById('root'));

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: http://bit.ly/CRA-PWA
serviceWorker.unregister();

import App from './App';

App.js의 컴포넌트를 사용하기 위해 App.js 파일을 위와 같이 import 시켜준다.

(물론 App.js에서도 export 선언을 해야 import 사용이 가능하다.)

이제 App.js 를 분석해보자.

import React, { Component } from 'react';
import './App.css';
import { css } from '@emotion/core';
import { ClipLoader } from "react-spinners";
import Movie from './Movie';

 

위 두 줄은 react-spinners (로딩바) 라이브러리를 사용하기 위해 import 시킨 것이다.

API로부터 영화 데이터를 다 받아오기전에 사용자에게 로딩화면을 보여 줄 때 사용할 라이브러리.

 

터미널에

yarn add react-spinners 로 먼저 라이브러리를 설치해준다.

( 또는 npm install react-spinners --save)

https://www.npmjs.com/package/react-spinners

react-spinners

A collection of react loading spinners

www.npmjs.com

먼저 컴포넌트(데이터 요소)를 렌더링하는 순서를 기억해야한다.

아래 순서대로 function이 호출된다.

각 function에 console.log를 찍으면 브라우저에서 아래 순서로 호출되는 것을 확인 할 수 있다.

1. 일반적인 데이터 로딩

componentWillMount() -> render() -> componentDidMount()

컴포넌트(데이터)를 mount 할 예정 -> 데이터 rendering -> 컴포넌트(데이터)를 mount 완료

2. 로딩된 데이터가 업데이트 될 경우

componentWillReceiveProps() -> shouldComponentUpdate() -> componentWillUpdate() -> render() -> componentDidUpdate()

업데이트 되는 상황은 좀 더 복잡하다.

일반적으로 React.js를 쓰면 장점이, 데이터가 위와같이 update 될 때, 전체 html을 렌더링 하지 않고, 변경된(또는 추가된) 부분만 데이터를 읽어와 해당하는 부분만 html을 렌더링 시킬 수 있다.

1번을 구현하기 위해, 각 function을 작성한다.

 

...
...

state = {}

componentWillMount() {
    console.log('will mount');
}

render() {  // Movie 컴포넌트를 불러온후 렌더링.
    console.log('did render');
    const { movies } = this.state;
    console.log(movies);
    return (
      <div className={movies ? "App" : "App--loading"}>
        {this.state.movies ? this._renderMovies() : this._loadingMovies()}
      </div>
    );
 }

 componentDidMount() {
    this._getMovies();
 }

...
...
}

 

 

"state" 는 데이터가 새롭게 추가되는지 확인할 수 있는 변수이다.

말 그대로 데이터 '상태'가 변화되었는지 체크할 수 있는 변수 용도로 사용된다.

return (
      <div className={movies ? "App" : "App--loading"}>
        {this.state.movies ? this._renderMovies() : this._loadingMovies()}
      </div>
    );

 

render() 를 보면, 위와같은 html을 렌더링 시킨다.

this.state.movies ? ...

문장은 state에 movies 값이 있으면 _renderMovies() function을 수행하고,

값이 존재하지 않으면 loadingMovies() function을 수행한다.

초기에는 state값에 movie가 없으므로 _loadingMovies() function을 실행한다.

* loadingMovies()는 단순히 시각효과만 나타내는 기능이라 skip하고,

위 function을 수행한후, componentDidMount() 가 실행된다.

componentDidMount()에서는 _getMovies() fuction을 수행한다.

그럼, 각 정의된 function의 역할을 보자.

_renderMovies = () => {
    const movies = this.state.movies.map((movie) => {
      return <Movie 
        title={movie.title}
        poster={movie.medium_cover_image} 
        key={movie.id} 
        genres={movie.genres}
        synopsis={movie.synopsis}
        rating={movie.rating}
      />
    })

    return movies;
  }

  // async function
  // callApi function으로부터 모든 데이터를 수신 완료하고 수행 
  _getMovies = async () => {  
    const movies = await this._callApi();   
    this.setState({
      movies
    });
  }

  _callApi = () => {
    return fetch('https://yts.am/api/v2/list_movies.json?sort_by=download_count')
    .then(response => response.json())    // fetch()가 성공적으로 끝났으면 then 문장을 실행
    .then(json => json.data.movies)   // _getMovies의 const movies 변수에 최종으로 전달(return)되는 데이터 
    .catch(err => console.log(err))             // fetch()가 오류가 나면, catch 문장을 실행
  }

  _loadingMovies = () => {
    const override = css`
    display: block;
    margin: 0 auto;
    border-color: red;
    align: center;
    margin-left: 20px;
    `;
    
    return (
      <div className="App--loading2">
        <ClipLoader
          css={override}
          sizeUnit={"px"}
          size={40}
          color={'#36D7B7'}
          loading={this.state.loading}
          />
          <p>Loading...</p>
      </div>
    )
     
  }

 

getMovies() function에서는

_callApi function을 호출하여, API를 통해 불러온 영화 데이터를 const movie 변수에 저장한다.

그리고 this.setState() 메소드를 통해 state값에 movie를 저장한다.

* state에 특정값을 전달하려면, 직접 this.state()... 와 같이 직접적으로 값을 저장하면 안되고,

간접적으로 setState() function을 통해 저장해야 한다.

전반적으로 로직은,

ComponentWillMount() -> render() -> _LoadingMovies() -> ComponentDidMount() -> _getMovies() -> _callApi() -> state 값 변경으로 인해 render() 재실행 -> _renderMovies() -> ComponentDidMount()

다소 복잡해보이지만, 차근차근 절차를 따라와보면 이해가 금방 된다.

핵심은 state값이 변경되면 render -> DidMount 로직을 재실행!

7번째부터 설명을 하자면,

getMovies()에서 state값이 변경되었으므로, render()를 재실행한다.

_getMovies에서 state값에 movies를 저장하였으므로,

render() function에서

<div className={movies ? "App" : "App--loading"}>
        {this.state.movies ? this._renderMovies() : this._loadingMovies()}
</div>

this._renderMovies() function이 실행된다.

div 태그의 className도 movies값(this.state) 이 존재하므로 "App" 이 적용된다.

renderMovies() 의 수행코드를 보자.

 

const movies = this.state.movies.map((movie) => {
      return <Movie 
        title={movie.title}
        poster={movie.medium_cover_image} 
        key={movie.id} 
        genres={movie.genres}
        synopsis={movie.synopsis}
        rating={movie.rating}
      />
    })

    return movies;

 

이제 여기서부터,

Movie 를 참고하기 때문에, 자식 컴포넌트인 Movie.js를 같이 참고해야 한다.

위 내용을 보면, 영화 리스트 데이터를 받아와 각 영화의 세부정보들을 Movie에 Props로 전달하여 리턴한다.

* 위와 같이 컴포넌트를 리턴하여 호출하려면, 각 컴포넌트에 필요한 props값들을 전달해줘야 한다.

그럼 Movie.js에 정의된 Movie의 구조를 보자.

 

function Movie({title, poster, genres, synopsis, rating}) {
    return (
        <div className="Movie"> 
            <div className="Movie__Column">
                <MoviePoster poster={poster} alt={title} />
            </div>
            <div className="Movie__Column">
                <h1>{title}</h1>
                <div className="Movie_Rating">
                    <StarRatings
                        rating={rating / 2}
                        starRatedColor="yellow"
                        numberOfStars={5}
                        name='rating'
                        starDimension="30px"
                        starSpacing="5px"
                        starEmptyColor="grey"
                    />
                </div>
                <div className="Movie_Genres">
                    {genres.map((genre, index) => <MovieGenre genre={genre} key={index} /> )}
                </div>
                <div className="Movie_Synopsis">
                    <LinesEllipsis
                        text={synopsis}
                        maxLine='3'
                        ellipsis='...'
                        trimRight
                        basedOn='letters'
                    />
                </div>
            </div>
            
        </div>
    );
}

 

각 영화정보를 출력해주는 html 구조를 리턴하는데,

Movie라는 커다란 무비 카드 뷰 컴포넌트 안에, MoviePoster , MovieGenre 자식 컴포넌트가 있다.

* <StarRatings, LinesEllipsis 컴포넌트는 각각 별점, 장문을 요약표시하는 라이브러리를 사용한 것이다.

자세한 사용방법은 구글링을 통해 검색해보자...

각 컴포넌트는 function Movie(...) 와 같이, 각 fuction으로 세분화 시키는 것이 바람직하다.

그럼 Movie의 각 자식 컴포넌트는 어떻게 정의되어 있는지 보자.

 

function MoviePoster({poster, alt}) {
    return (
        <img src={poster} alt={alt} className="Movie__Poster"/>
    );
}

function MovieGenre({genre}) {
    return (
        <span className="Movie_Genre">{genre} </span>
    )
}

 

Movie 컴포넌트와 크게 다를게 없다. 매개변수로 props 값을 받아 해당 props값을 html과 함께 출력해준다.

* 그런데 위와 같이 각 컴포넌트에 특정 props값들의 타입이나, 반드시 필요하다고 정의할 필요가 있다.

그렇다면, 각 컴포넌트에 대한 propTypes을 아래와 같이 작성한다.

 

Movie.propTypes = {
    title: PropTypes.string.isRequired,
    poster: PropTypes.string.isRequired,
    genres: PropTypes.array.isRequired,
    synopsis: PropTypes.string.isRequired,
    rating: PropTypes.number.isRequired
}

MoviePoster.propTypes = {
    poster: PropTypes.string.isRequired,
    alt: PropTypes.string.isRequired
}

MovieGenre.propTypes = {
    genre: PropTypes.string.isRequired
}

 

위와같이 propTypes를 정의해두면, 각 컴포넌트에 요구되는 propTypes를 정리해둘 수 있다.

이렇게 하여 MoviePoster, MovieGenre -> Movie -> App 컴포넌트 구조로 데이터가 렌더링된다.

(별점, 로딩바 라이브러리 추가는 강의에 미포함된 내용으로, 추가 응용하여 구현한 내용이다.)

다음엔 React.js를 통해 인스타그램 클론 코딩을 올려봐야겠다.

* 전체 코드 보기

https://github.com/JisooJang/ReactJS_Movie_app

 

JisooJang/ReactJS_Movie_app

practicing ReactJS by developing reviewing Movie app - JisooJang/ReactJS_Movie_app

github.com

 

 

 

* 완성 데모 화면 보기

https://jisoojang.github.io/ReactJS_Movie_app/index

 

Movie App

 

jisoojang.github.io

* 질문이나, 잘못된 부분이 있으면 언제든 피드백 주세요 :)

관련글 더보기

댓글 영역