지난포스트 (deprecated)/Result (deprecated)

Nomadcoder Course - ReactJS Create Movie App

.log('FE') 2018. 11. 7. 01:57
728x90
반응형

 

 

한번 들었던 강의지만 리마인드도하고 React를 좀 잊어가는것같아서 빠르게 복습하면서 블로그에 진행사항을 남기려고 한다.

 

GIT : https://github.com/kangyongseok/portfolio/tree/master/src/pages/movie

DEMO : https://react-potfolio.firebaseapp.com/movie

 

 

# npm install -g create-react-app
# create-react-app 폴더명
# cd 폴더명
# npm start


위의 명령어를 명령프롬프트창에 입력하고 새로운 react-app 폴더를 생성한다.

 


 

 

설치에 성공하면 볼수있게 된다.

 

해당 폴더로 들어가서 # npm start 를 입력하게되면 localhost:3000 을 가진 웹페이지가 하나 뜨게되고

 

React 로고가 뱅글뱅글 돌아가는 페이지가 뜨면 정상이다.

 

 

 

# create-react-app 명령어는 별다른 설정없이 간단하게 웹 코딩을 바로 시작할수 있게 해준다. 코드 수정시 실시간으로 수정사항을 확인하면서 작업이 가능하다.

 

여기서는 React 컴포넌드를 JSX 라는 문법을 활용하여 html 을 작성하게 된다.

 

 

아무것도 작업하지 않은상태에서 create-react-app 폴더명 했을때 나오는 파일 트리 구조이다.

 

여기서 App.js 파일안에서 className="App" 내부에있는 코드는 다 지우고 시작한다.

 


class App extends Component {
render() {
return (
<div className="App">

 

</div>
);
}
}


App.css 폴더안에 작성된 내용도 삭제한다.

 

src/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'));


serviceWorker.unregister();

 

이렇게 작성이 되어있는데

 

import 는 해당 모듈을 불러온다는 명령어이다.

 

React 모듈은 UI를 담당하고 있고 ReactDOM 은 웹페이지에 우리가 입력한 코드가 표시될수 있도록 해준다.

 

App.js App.css 에서 수정하고 입력한것들이 index.js 를통해 public/index.html 에 출력되도록 해준다.

 

public/index.html 에는 단지

 

<div id="root"></div>


만 있을 뿐이다.

 


 

 

Movie Component Create

 

MovieApp 을 만들기 위해서 컴포넌트를 생성해야한다.

 

src/Movie.js 와 Movie.css 파일을 생성한다.

 

Movie.js

import React, { Component } from 'react';
import './Movie.css';


class Movie extends Component {
render() {
return (
<h1>Hello Movie</h1>
)
}
}


export default Movie;

 

import 는 모듈을 불러오고 export 는 외부에서 새로만든 컴포넌트를 사용할수있게 내보내주는 역할을 한다.

 

render 는 괄호안에있는 코드를 랜더링 한다는 것이고

 

return 은 해당 html 코드를 되돌려주는 열학을 하게 된다.

 

class Movie 를 생성하여 Component 로 확장하는 코드를 작성하고 그 안에 render return 을 작성하면 된다.

 

App.js

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import Movie from './Movie';


class App extends Component {
render() {
return (
<div className="App">
<Movie />
</div>
);
}
}


export default App;



App.js 로와서 상단에 import 로 방금만든 Movie 컴포넌트를 사용할 수 있도록 추가하고

 

태그를 사용하듯이 <Movie /> 를 추가해주면 Movie.js 에서 입력한 내용이 페이지에 나타난다.

 

 

 

 

Parent Component to Child Component

 

컴포넌트를 생성해서 상위컴포넌트로 넘겨주는건 해보았으니 이제 부모컴포넌트에서 자식컴포넌트로 정보를 어떻게 받아올지 보려고 한다.

 

App.js

영화제목을 담은 배열을 하나 만들고

const movies = [
"Matrix",
"Oldboy",
"Start Wars",
"Full Metal Jacket"
]

 

 

하단에 Movie Component 에 title 이라는 속성으로 배열로 정의한 제목을 할당해 준다.

class App extends Component {
render() {
return (
<div className="App">
<Movie title={movies[0]} />
<Movie title={movies[1]} />
<Movie title={movies[2]} />
<Movie title={movies[3]} />
</div>
);
}
}

 

 

Movie.js

 

자식컴포넌트인 Movie.js 파일에 Movie 클래스에 this.props.title 로 부모컴포넌트에서 속성명으로 추가한 title 을 불러온다.

 

class Movie extends Component {
render() {
return (
<div>
<MoviePoster />
<h1>{this.props.title}</h1>
</div>
)
}
}



 MoviePoster 는 영화 포스터를 불러오기위해 하단에 정의한 컴포넌트이다.

 

class MoviePoster extends Component{
render() {
return (
<img src="https://t1.daumcdn.net/cfile/tistory/2378814B549EBD1B09" />
)
}
}



this.props.title 에서의 {} 중괄호는 ES6 에서 추가된 새로운 문법으로 html 코드안에 있어도 중괄호안에있는 코드는 javascript 처럼 실행되도록 만들어준다.

 

해당코드까지 모두 입력되었으면 화면에는

 

이미지

영화제목1

 

이미지

영화제목2

 

이런식으로 총 4개가 생성되었을거고 이미지는 현재 동일한 이미지가 4장으로 보일것이다.

 

이제 제목에 맞춰서 영화 포스터도 바꿔 보려고 한다.

 

 

App.js

 

영화 타이틀을 만들어 주었던것처럼 하단에 movieImages 로 배열을 만들고 해당되는 포스터 이미지의 URL 을 넣는다. 심각하게 주소가 길어져서 샘플명으로 추렸다.

const movieImages = [

"moviePoster1.jpg",

"moviePoster2.jpg",

"moviePoster3.jpg",

"moviePoster4.jpg"

]



App Component title 을 추가했듯이 poster 란 속성명으로 movieImages 의 정보를 받아온다.

 

class App extends Component {
render() {
return (
<div className="App">
<Movie title={movieTitles[0]} poster={movieImages[0]} />
<Movie title={movieTitles[1]} poster={movieImages[1]} />
<Movie title={movieTitles[2]} poster={movieImages[2]} />
<Movie title={movieTitles[3]} poster={movieImages[3]} />
</div>
);
}
}

 

 

 

 

Movie.js

 

Movie Component 에서 자식 컴포넌트인 MoviePoster 에 속성명으로 this.props.poster 를 넣어 부모컴포넌트에서 정보를 받아오고

 

class Movie extends Component {
render() {
console.log(this.props) 
return (
<div>
<MoviePoster poster={this.props.poster} />
<h1>{this.props.title}</h1>
</div>
)
}
}

 

 

그 하위의 자식컴포넌트인 MoviePoster Component 의 img 태그에 src 속성에 this.props.poster 로 값을 다시 받아온다.

 

class MoviePoster extends Component{
render() {
console.log(this.props)
return (
<img src={this.props.poster} />
)
}
}

 

저렇게 console.log(this.props) 를 해두면 어떤 컴포넌트를 받아오는지 확인이 가능하다.

 

Movie 컴포넌트에서는 console 에 출력하면

 

 

이렇게 title 과 poster를 객체로 받아오는것을 알수있다.

 

MoviePoster Component 에서는 console 에 출력하면

 

 

이렇게 poster 정보만 받아온다.

 

src 속성에는 this.props 만 넣어도 출력이 될것같이 보이지만 실제로 실행해보면 이미지가 뜨지않는다.

 

이유는 객체형태로 받아오기때문에 실제 주소값이 입력되는것이 아니다.

 

따라서 this.props.title 또는 this.props.poster 로 받아올 정보를 정확히 명시를 해주어야 한다.

 

props 를 활용하면 최상위 부모페이지에서 모든 데이터 정보를 입력하고 그걸 자식컴포넌트에 무한정 뿌려주는것이 가능하다.

 

 

 

 

 

List with map

 

현재 상태라면 데이터 관리도 어렵고 또 데이터가 추가될때마다 App Component 에 코드가 한줄씩 계속 추가 되어야 한다. 이 부분을 해결하고자 데이터를 하나의 변수에 객체배열로 넣고 map 이라는 내장함수를 사용하여 데이터가 몇개가 추가되든 코드 한줄로 처리할 수 있도록 하려고 한다.

 

기존의 movieTitle   moviePoster 의 내용을 movie 라는 하나의 변수에 객체배열로 담는다.

 

App.js

const movies = [
{
title: "Matrix",
poster: "poster1.jpg"
},
{
title: "Oldboy",
poster: "poster2.jpg"
},
{
title: "Start Wars",
poster: "poster3.jpg"
},
{
title: "Full Metal Jacket",
poster: "poster4.jpg"
}
]

 

 

 

그리고 하단의 App Component 의 코드를 수정한다.

class App extends Component {
render() {
return (
<div className="App">
{movies.map(movie => {
return <Movie title={movie.title} poster={movie.poster} />
})}
</div>
);
}
}



movies 의 배열을 map 함수로 각각 배열을 돌면서 값을 가져와 나타내도록 하였다. movie 라는 파라미터로 받아와 movie.title movie.poster 를 사용할 수 있다.

 

화면에 보이는 결과물은 이전과 동일하지만 내부코드는 조금더 깔끔해졌다.

 

그러나 콘솔을 열어보면 ERROR 가 떠있다.

 

map 함수는 새로운 배열을 생성해 주는것인데 경고문구를 읽어보면 각각의 자식요소배열안에 특별한 'key'를 가져야 한다고 나와있다.

 

React에서는 Element가 많을경우 고유한 key 값을 주어야 한다고 한다.

 

map에서 받아올수있는건 index 값도 있기때문에 이걸 받아서 key에 추가해 주면 에러메시지가 사라진다.

class App extends Component {
render() {
return (
<div className="App">
{movies.map((movie, index) => {
return  <Movie title={movie.title} poster={movie.poster} key={index} />
})}
</div>
);
}
}


 

 

 

 

PropTypes

 

Parent Component 에서 받은 정보의 데이터 타입 확인과 필수요소로의 지정을 할 수 있다.

잘못된 데이터를 전달받았을 경우 에러메시지로 확인이 가능하며 누락되었을경우도 콘솔창에서 확인이 가능하다.

 

# npm install --save prop-types

 


별도의 모듈을 설치한 후 Child Component 내부에서 코드를 작성해 주면 된다.

 

Movie.js

 

class Movie extends Component {


static propTypes = {
title: PropTypes.string.isRequired,
poster: PropTypes.string.isRequired
}


render() {
// console.log(this.props)
return (
<div>
<MoviePoster poster={this.props.poster} />
<h1>{this.props.title}</h1>
</div>
)
}
}


모듈을 설치해서 사용하는것이기 때문에 사용할 페이지 상단에 import 문구를 추가해 주어야 한다.

 

import PropTypes from 'prop-types';


 

 

 

Component LifeCycle

 

Render

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

 

Update

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

 

 

 

 

 

State

 

React 는 State 를 통해서도 데이터를 처리할 수 있다. state 에서 기본 정보를 정의하면 setState 를 통해서 데이터의 추가 및 변경이 가능하다. 물론 이 모든건 컴포넌트 내부에서 이루어 진다.

 

App.js

 

App Component 내부에 state로 데이터를 정의해주었다.

state = {
greeting: 'Hello!',
movies: [
{
title: "Matrix",
poster: "poster1.jpg"
},
{
title: "Oldboy",
poster: "poster2.jpg"
},
{
title: "Start Wars",
poster: "poster3.jpg"
},
{
title: "Full Metal Jacket",
poster: "poster4.jpg"
}
]
}

 

 

그리고 setState 의 변화를 확인하기 위해서 componentDidMount() 를 활용한다. componentDidMount() 는 해당 컴포넌트의 랜더링이 정상적으로 끝났을때 확인이 가능하다.

 

componentDidMount() {
setTimeout(() => {
this.setState({
movies: [
...this.state.movies,
{
title:"transporting",
poster: "poster5.jpg"
}
]
})
}, 1000)
}



해당 함수는 랜더링이 끝난후 1초뒤에 setState 에 정의된 데이터를 추가하게 된다.

만약 이부분이 없었다면

...this.state.movies


데이터의 추가가 아닌 기존 데이터는 모두 사라지고 setState 로 인해 추가된 데이터만 남아있게된다.

 

redner 함수에도 코드의 수정이 필요하다

render() {
return (
<div className="App">
{this.state.movies.map((movie, index) => {
return <Movie title={movie.title} poster={movie.poster} key={index} />
})}
</div>
);
}


 

 


Loading & State

 

state 와 setState 를 이용하여 데이터의 추가를 setTimeout 을 이용하여 시간차 적용을 해 보았는데

이번에는 흔히 페이지의 컨텐츠가 나오기 전 로딩페이지의 구현방식을 해보려고 한다.

 

필요한것

state = {}

setState()

setTimeout()

componentDidMount()

_renderMovies()

 

state 는 비어있는 객체로 만들어두고 componentDidMount() 로 랜더링이 끝난 후 발생시킬 setTimeout 함수를 정의합니다.

state = {

 

}

 

setTimeout 은 5초후에 setState() 의 내부에있는 데이터를 화면에 표시하는 역할을 합니다.

componentDidMount() {
setTimeout(() => {
this.setState({
movies: [
{
title: "Matrix",
poster: "poster1.jpg"
},
{
title: "Oldboy",
poster: "poster2.jpg"
},
{
title: "Start Wars",
poster: "poster3.jpg"
},
{
title: "Full Metal Jacket",
poster: "poster4.jpg"
}
]
})
}, 5000)
}

 

 

 

_renderMovies() 함수는 기존에 render 가 갖고있던 화면에 내용을 출력하는 코드를 갖습니다.

_renderMovies = () => {
const movies = this.state.movies.map((movie, index) => {
return <Movie title={movie.title} poster={movie.poster} key={index} />
})
return movies
}

 

 

render() 는 조건문을 통하여 state 에 movie 라는 변수를 가진 객체데이터가 없으면 'Loding' 데이터가 추가되면 그 데이터를 화면에 보이라는 명령을 갖게 됩니다.

render() {
return (
<div className="App">
{this.state.movies ? this._renderMovies() : 'Loading'}
</div>
);
}

 

 

 

state Component VS stateless Component

 

state 는 컴포넌트 내부에서 동작하게 때문에 Component Data Flow 흐름에 따르게되고 Component 는 class 로 정의되어야 하며 render() 함수가 필요하다.

 

하지만 꼭 state가 필요한것은 아니기에 적절한 쓰임이 필요하다.

 

state 없이 부모에서 정의된 데이터를 받아오려면 function() 을 활용하면된다.

 

Movie.js

function Movie({title, poster}) {
return (
<div>
<MoviePoster poster={poster} />
<h1>{title}</h1>
</div>
)
}

function MoviePoster({poster}) {
return (
<img src={poster} alt="Movie Poster" />
)
}

Movie.propTypes = {
title: PropTypes.string.isRequired,
poster: PropTypes.string.isRequired
}

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



함수로 파라미터값을 부모 컴포넌트로부터 받아와 return 해주고 type 설정도 별도로 가능하다.

 

이렇게 단순히 return 만해주는 역할이라면 class component 를 가져갈 이유가 없고 훨씬 간결한 코드가 된다.

 

 

 

 

AJAX

 

데이터를 이제 직접 입력이 아닌 API 를 가져와 원하는 데이터만을 뽑아 내어 화면에 표시하기위해 AJAX 를 다루려고 한다.

 

App.js

componentDidMount() {
fetch('https://yts.am/api/v2/list_movies.json?sort_by=rating')
.then(response => response.json())
.then(json => console.log(json))
.catch(err => console.log(err))
}



componentDidMount() 내부에서 fetch then, catch 를 사용한다.

 

fetch 는 JSON(Javascript Object Notation) 으로 된 API 정보를 쉽게 저렇게 url 하나로 쉽게 받아 올수있다.

 

그리고 .then() 을통해서 fetch 의 작업이 완료되면 .then() 을 실행하게 되고 에러가 발생하면 catch 를 실행하게 된다. 이러한 것을 promise 라고 한다.

 

 

 

정상적으로 불러와서 실행이 되었다면 콘솔창에서 확인 할 수 있는 정보이다.

 

status: "ok" , status_Message: "Query was successful" 이라고 나오는걸 확인하고 아래에 data -> movies 에 보면 배열로 API 로 가져온 영화의 정보들이 담겨져있다.

 

이 영화 정보들을 화면에 출력해주기 위해서는 다음의 작업이 필요하다.

 

우선 두개의 함수를 만든다.

 

 

App.js

 

_getMovies = () => {
// 영화정보를 setState 해줌
}


_callApi = () => {
// componentDidMount() 에 들어갈 정보를 따로 분리해서 역학을 쪼갬
}

 

_callApi() componentDidMount() 내부를 간결과 하기 위해 별도로 분리시킨 함수이다.

 

_callApi = () => {
return fetch('https://yts.am/api/v2/list_movies.json?sort_by=rating')
.then(response => response.json())
.then(json => json.data.movies)
.catch(err => console.log(err))
}

 

최종적으로 리턴하는 결과값은 API 에 movies 객체 내부에 존재하던 영화정보 배열이다.

 

_getMovies() 는 비동기 방식으로 처리할 함수이고 _callApi() 를 받아와 처리하게 된다.

 

_getMovies = async () => {
const movies = await this._callApi()
this.setState({
movies
})
}

 

await _callApi() 가 실행될때까지 기다린다. 성공 실패여부와 상관없이 작동이 완료될때까지 기다린후 movies 라는 변수에 그 값을 넣어준다.

 

만약 이 방식을 쓰지않는다면 _callApi() 함수가 처리되기전에 함수가 실행되고 그러면 에러가 발생하게된다.

 

 

componentDidMount() 내부에서는 _getMovies() 함수를 실행시킨다.

 

componentDidMount() {
this._getMovies();
}

 

_redenrMovies() 에서도 이제 key 값을 index 가 아닌 movies 에있는 id값으로 대체하려고 한다.

이유는 index 처리가 느리기때문이다.

 

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

 

여기까지하면 API 로 영화정보를 받아와 화면에 뿌려주는것까지 마무리가 되었다.

 

이제부터 할일은 CSS 로 며주기만 하면된다.

 

 

 

728x90
반응형