#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
* 완성 데모 화면 보기
https://jisoojang.github.io/ReactJS_Movie_app/index
* 질문이나, 잘못된 부분이 있으면 언제든 피드백 주세요 :)
[Spring] Aspected-Oriented Programming(AOP) 개념과 종류 정리 (0) | 2024.02.28 |
---|---|
[CS] 트랜잭션의 기본 개념과 격리 수준, 전파 옵션 + Spring의 선언적 트랜잭션 관리, 분산 트랜잭션 1부 (0) | 2024.02.19 |
[git] git rebase로 commit 정리하기 기록용 (1) | 2024.01.14 |
[java] hashcode()와 equals() 메서드는 언제 사용하고 왜 사용할까? (4) | 2020.07.01 |
댓글 영역