app.css에 몇가지 규칙을 추가하겠습니다.


.App {
padding:50px;
display: flex;
justify-content: space-around;
flex-wrap: wrap;
font-size:14px;
}


.App--loading{
display: flex;
justify-content: center;
align-content: center;
height: 100%;
}


아래는 전 시간까지 작성하였던 app.js 파일입니다.


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




class App extends Component {
//Render : componentWillMount() -> render() -> componentDidMount()
//Update : componentWillReceiveProps() -> shouldComponentUpdate() -> componentwillUpdate() -> render() -> componentDidUpdate

state = {};

componentDidMount() {
this._getMoives();
}

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

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

_callApi = () => {
.then(response => response.json())
.then(json => json.data.movies)
.catch(err => console.log(err));
};

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

export default App;




아래와 같이 일부 수정을 해주었습니다.

여기서 수정한 것은  app 컴포넌트의 클래스 이름입니다.


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


state에 movies 가 있는지 물어봅니다.

movies가 있으면 div의 이름은 app , movies가 없으면 app-loading이라고 정했습니다.

movie.js 파일도 아래와 같이 수정 하였습니다.


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

function Movie({ title, poster, genres, synopsis }) {
return (
<div className="Movie">
<div className="Movie__Column">
<MoviePoster poster={poster} alt={title} />
</div>
<div className="Movie__Column">
<h1>{title}</h1>
<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>
);
}

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

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

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

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

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

export default Movie;



그리고 index.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, #root{
height: 100%;
}


폰트, 배경색, 높이를 정해줬습니다.

movie.css 도 아래와 같이 수정해주었습니다.

배경색, 박스쉐도우, 마진, 플렉스, 그리고 movie column도 조정했습니다.

그리고 영화제목 css, 장르, 시놉시스, 포스터에 쉐도우도 넣었습니다.


.Movie{
background-color:white;
width:40%;
display: flex;
justify-content: space-between;
align-items:flex-start;
flex-wrap: wrap;
margin-bottom: 50px;
text-overflow: ellipsis;
padding:0 20px;
box-shadow: 0 8px 38px rgba(133,133,133,0.3),0 5px 12px rgba(133,133,133,0.22);
}

.Movie__Column{
width: 30%;
box-sizing: border-box;
text-overflow: ellipsis;
}

.Movie__Column:last-child{
padding: 20px 0;
width:60%;
}

.Movie h1{
font-size:20px;
font-weight: 600;
}

.Movie .Movie__Genres{
display: flex;
flex-wrap: wrap;
margin-bottom: 20px;
}

.Movie__Genres .Movie__Genre{
margin-right: 10px;
color: #B4B5BD;
}

.Movie .Movie__Synopsis{
text-overflow: ellipsis;
color: #B4B5BD;
overflow: hidden;
}

.Movie .Movie__Poster{
max-width: 100%;
position: relative;
top:-20px;
box-shadow: -10px 19px 38px rgba(83, 83, 83, 0.3), 10px 15px 12px rgba(80, 80, 80, 0.22);
}


아래와 같이 responsive 작업도 해주었습니다.


@media screen and (min-width:320px) and (max-width:667px){
.Movie{
width:100%;
}
}

@media screen and (min-width: 320px) and (max-width: 667px) and (orientation: portrait){
.Movie{
width:100%;
flex-direction: column;
}
.Movie__Poster{
top:0;
left:0;
width:100%;
}
.Movie__Column{
width:100%!important;
}
}


위에는 모바일 폰, 아래는 모바일-portrait 모드로 작업을 하였습니다.

이렇게 코딩을 모두 완료하였고 화면을 확인해보겠습니다.






화면을 줄이면 자동으로 보여지는 화면도 수정되는 것을 아래와 같이 확인할 수 있습니다.






이번시간까지 해서 총 20번의 연재를 마쳤습니다. 높은 품질의 결과물은 아니였어도 강의를 따라하면서 

끝까지 결과물을 완성할 수 있다는 것에 의미를 두고 싶습니다.

부족하였지만 여러분들에게도 조금이나마 도움이 되는 글들이였기를 바랍니다.

고맙습니다.


이제 우리 페이지에 영화목록이 뜨고 있습니다.


우리의 컴포넌트를 업데이트 해야 합니다.

처음에는 데이터를 받아야 하고 두번째는 css를 해야 합니다.

일단 요소 검사를 해서 우리가 어떤 종류의 정보를 받고 있는지 살펴 보겠습니다.

그전에 우리가 필요한 것은 포스터, 제목, 장르, 평점, 설명입니다.




업데이트를 해보겠습니다.

제목의 경우 title_english 를 불러 오겠습니다.

포스터는 small_cover_image 를 불러 오겠습니다.

장르는 genres 를 불러 오겠습니다.

설명은 synopsis 를 불러 오겠습니다.

아래는 app.js 파일 중에 일부입니다.

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

그리고 movie.js 파일을 수정해줍니다.

기존 파일은 아래와 같습니다.


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

/*
class Movie extends Component {
static propTypes = {
title: PropTypes.string.isRequired,
poster: PropTypes.string.isRequired
}

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

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


/*
class MoviePoster extends Component {
static propTypes = {
poster: PropTypes.string.isRequired
}

render(){
return (
<img src={this.props.poster} alt="Movie Poster" />
)
}
}
*/

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
}

export default Movie


아래와 같이 수정해줍니다.

prop types 다음에 장르, 시놉시스를 추가해줍니다.


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

/*
class Movie extends Component {
static propTypes = {
title: PropTypes.string.isRequired,
poster: PropTypes.string.isRequired
}

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

function Movie({title, poster,genres, synopsis}){
return(
<div className="Movie">
<div className="Movie__Columns">
<MoviePoster poster={poster} alt={title}/>
</div>
<div className="Movie__Columns">
<h1>{title}</h1>
<div className="Movie__Genres">
{genres.map((genre, index) => <MovieGenre genre={genre} key={index} />)}
</div>
<p className="Movie__Synopsis">
{synopsis}
</p>
</div>
</div>
)
}




/*
class MoviePoster extends Component {
static propTypes = {
poster: PropTypes.string.isRequired
}

render(){
return (
<img src={this.props.poster} alt="Movie Poster" />
)
}
}
*/

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

)
}

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

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

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

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


export default Movie


그외에도 이미지위에 마우스를 올려놓으면 타이틀이 보이게 추가하였습니다.

캡쳐를 못했지만 위와 같이 코딩을 완료하시고 마우스를 올려놓으면 제목이 출력되는 것을 

확인하실 수 있으실 겁니다.





이번시간에 코딩한 것을 복습해보겠습니다.

이번시간에는 장르와 시놉시스 같은 props를 더 추가하였습니다.

이전에는 포스터와 제목밖에 없었습니다.

props를 추가하고 체크할 수 있도록 proptype에 등록 했습니다.

그다음 베이직 html작업을 하였습니다. 

클래스명도 JSX로  추가하였습니다.

칼럼-무비포스터도 만들고, 제목도 만들었습니다. 장르 array를 맵핑하고

무비 시놉시스 클래스를 만들었습니다. 그리고 alt 이미지도 만들었습니다.

그래서 이미지의 제목이 뜨게 된 것입니다.

그리고 장르를 맵핑할 때 movie genre라는 새로운 functional 컴포넌트를 만들었습니다.

간단한 span을 return 하는 기능이었습니다.


이번시간은 여기까지하고 마치겠습니다. 고맙습니다.










await, async를 배울 차례 입니다.


await, async는 지난 시간에 작성한 아래 라인들을 조금 더 분명하게 작성해 주는 도구입니다.

componentDidMount() {
.then(response => response.json())
.then(json => console.log(json))
.catch(err => console.log(err))
}


영화들을 state에 올리려면 다음과 같은 작업을 해야 합니다.

componentDidMount() {
.then(response => response.json())
.then(json => {
this.setState({
movies: json.data.movies
})
})
.catch(err => console.log(err))
}


세련되보이지 않습니다. 그리고 애플리케이션이 크면 then 안에 then으로 이어지면서,

call back hell(콜백지옥)에 빠지게 됩니다.

그렇기 때문에 여기서 async, await이라는 것을 쓰게 될겁니다.

위와 같이 코딩하는 대신 새로운 get movies 와 callApi 라는 function을 만들 것입니다. 


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




class App extends Component {
//Render : componentWillMount() -> render() -> componentDidMount()
//Update : componentWillReceiveProps() -> shouldComponentUpdate() -> componentwillUpdate() -> render() -> componentDidUpdate

state = {};

componentDidMount() {
this._getMoives();
}

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

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

_callApi = () => {
.then(response => response.json())
.then(json => json.data.movies)
.catch(err => console.log(err));
};

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

export default App;




await의 기능은 call api 기능이 끝나는 것을 기다리는 것입니다. 

성공적으로 수행했을 때만 수행되는 것이 아니고 call api의 return value가 무엇이든지 

그 값을 movies에 넣는다는 것입니다. 

그리고 get movies 컴포넌트의 set state를 movies 로 할 것입니다. 

그 것은 call api의 return value입니다.

set state는 call api작업이 완료되기 전까지는 실행되지 않을 것입니다.

위와 같이 코딩을 완료한 후에 인터넷창을 확인해보겠습니다.






영화의 제목은 왼쪽과 같이 나오지만 포스터가 출력되지 않고 있습니다.

에러메세지를 보면 포스터는 필요한 항목이라고 적혀있는데 value가 undefined 되어 있다라고 나옵니다.

그 이유는 movie 오브젝트가 변경되었기 때문입니다.


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


위에 부분에서 poster를 아래와 같이 large_cover_image 로 변경해줍니다.


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


수정을 완료하였으면 다시 화면을 확인해보겠습니다.






정상적으로 포스터 이미지까지 잘 출력되는 것을 확인하였습니다.

오늘 수정한 것들을 복습해보겠습니다.

첫번째 작업은 fetch를 call api로 변경하였습니다.

그 다음 get movies라는 function을 사용하였습니다.

get movies는 asynchronous function입니다.

그 안에 movies라는 이름의 const variable을 생성하였습니다. 

그리고 call api 작업이 완료되고 return 하기를 await 했습니다.

call api는 fetch promise 를 return 할 것 인데 이 것은 모든 데이터의 제이슨 이었습니다.

그 안에 movies 가 있는 data라는 이름의 오브젝트도 함께 있었습니다.

그래서 json.data.movies 라는 value는 const movies의 결과값이 되는 것입니다.

그리고 _getMovies 컴포넌트의 state를 movies로 set 한 것입니다.

state 안에 movies가 있으면, render moives라는 function을 불러오라는 명령도 해주었습니다.

그리고 render movies는 movies라는 const를 불러오는데 

이 부분은 영화 타이틀, 포스터(large_cover_image)순으로  매핑을 해줍니다.


그리고 마지막으로 아래와 같이 index 부분을 movie.id로 수정해줍니다.

그 이유는 컴포넌트의 key는 인덱스를 사용하는 것이 느리기 때문 입니다. 

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



이번 시간은 여기까지 입니다. 고맙습니다.



promise는 새로운 자바스크립트 컨셉입니다.


promise가 좋은 이유는 asynchronous programming 때문입니다.

asynchronouse에 대한 예를 들어보겠습니다.

아래에 라인이 두개가 있습니다.

componentDidMount() {
console.log('hello')
}

두번째 라인 hello 는 첫번째 라인 fetch가 끝나지 않으면 실행되지 않습니다.

이런 방식을 synchronous(동기)라고 합니다.

좋을 때도 있지만 좋지 않을 때도 있습니다.

왜냐하면 예를 들어서 내가 영화를 불러오고 싶고, 동시에 set state, call, component 등등을 하고 싶습니다.

하지만 이 모든 작업을 할 수 없습니다. 왜나하면 첫번째 라인 작업이 끝날때까지 기다려야 하기 때문입니다.

여러분이 두 개의 db를 불러온다고 생각보세요. 한 개는 영화, 나머지 한 개는 애니메이션입니다.

 synchronous(동기) 방식일 경우 영화 API작업이 끝나야 애니메이션 API를 불러올 수 있다는 것입니다.

이 방식이 안 좋은 이유는 만약 영화서버가 느리다면 애니메이션은 계속 기다려야 합니다.

이런 방식을 해결하기 위해서 promise를 사용합니다.

promise는 asynchronous 입니다. 첫번째 라인이 다 끝나지 않아도 두번째 라인이 작업을 할 수 있다는 뜻입니다.

이 방식이 좋은 이유는 계속 다른 작업을 스케쥴 해 놓을수 있기 때문입니다. 

또 다른 promise의 기능은 시나리오를 잡는 방법을 만들어 줍니다. 

좋은 시나리오 혹은 나쁜 시나리오 ,

두 가지 시나리오가 있고 이를 관리하는 것입니다. 

fetch , promise가 좋은 이유는 시나리오가 생기고 이를 관리할 수 있기 때문입니다.

 그럼 promise를 적용해 보겠습니다.

조금 어려우실 수도 있는데 일단 코드를 보시면 이해가 되실 겁니다.

componentDidMount() {
.then()
.catch(err => console.log(err))
}

위의 fetch라인이 완료되면, 뭔가를 하라는 것입니다. 

그런데 위의 라인이 에러가 있으면 잡아서(catch) 알려달라는 것입니다.

then function은 1개의 attribute만 줍니다. 

아래와 같이 코딩을 한 후에 console 을 확인해보겠습니다.

componentDidMount() {
.then(response => console.log(response))
.catch(err => console.log(err))
}





headers가 있고 ok:true, request가 성공적이었다는 뜻입니다.

그리고 redirected되지 않았고 status 200, '200'의 뜻은 ok라는 뜻입니다.

텍스트는 없고, 그리고 url을 확인합니다.

즉 , 여러분이 이 url을 불렀고 성공적이었으며, 다 좋다는 뜻입니다.

componentDidMount() {
.then(response => response.json())
.then(json => console.log(json))
.catch(err => console.log(err))
}



위와 같이 json 방식으로 바꾸고 다시 console 창을 확인해보겠습니다.





위와 같이 메세지가 확인 되었습니다.

화살표를 누르면 조금 더 자세한 아래 화면을 확인하실 수 있습니다.





데이터를 확인해보겠습니다. 6678개의 영화, 20줄, 그리고 영화를 다 보실 수 있습니다.

영화id, code, title등이 다 확인됩니다. 

response로 체크하고, json으로 변환하고 console로 확인된 것입니다.

이번시간은 여기까지입니다. 고맙습니다.




이제 ajax를 공부할 시간입니다. 


ajax, Asynchronous JavaScript and XM의 약자입니다.

요즘은 XML 별로 안쓰고 우리가 쓰게될 포멧은 JSON입니다.

JSON은 JavaScript Object Notation 입니다.

오브젝트를 자바스크립트로 작성하는 기법입니다.

AJAX를 React에 적용하는 방법은 심플합니다.

FETCH 덕분입니다.

FETCH 는 영어로 뭔가를 잡는 것을 뜻합니다.

그럼 fectch request를 만들어볼까요?

저희가 만든 fetch request는 url로 갈겁니다.

http request에 관한 방법이 많은데 

이 연재시간에는 그 방법들이 어떻게 리액트와 작업되는지 다 보지는 않을겁니다. 

 이 연재시리즈에서는 GET에 대해서만 이야기할 것입니다.

저희는 FETCH를 이용해서 URL에서 무엇인가를 GET하는 방법을 배울겁니다.

저희가 사용할 URL은 연재 초반에 이야기한 API -YTS 토렌트 영화 데이터베이스에서 갖고올겁니다.

아래 주소에 접속하시면 엄청 큰 자바스크립트 오브젝트를 보실 수 있으실 겁니다. 



이걸 리액트에서 불러올 수 있어야 합니다.

 작업을 해 보겠습니다. 이전에 작업한 timeout, 매트릭스 , 올드보이 .. 영화들 대신에, 

삭제를 하고 다시 작업을 하도록 하겠습니다. 진짜 영화DB를 원하기 때문입니다.

삭제를 하기 전에 App.js 파일은 아래와 같습니다.

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




class App extends Component {
//Render : componentWillMount() -> render() -> componentDidMount()
//Update : componentWillReceiveProps() -> shouldComponentUpdate() -> componentwillUpdate() -> render() -> componentDidUpdate
state = {
}

componentDidMount() {
setTimeout(() => {
this.setState({
movies: [
{
title: "Matrix",
poster:
},
{
title: "Full Metal Jacket",
poster:
},
{
title: "Oldboy",
poster:
},
{
title: "Star Wars",
poster:
}
]
})
}, 5000)
}

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

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

export default App;




삭제를 하고 난 후에 파일은 아래와 같습니다.


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




class App extends Component {
//Render : componentWillMount() -> render() -> componentDidMount()
//Update : componentWillReceiveProps() -> shouldComponentUpdate() -> componentwillUpdate() -> render() -> componentDidUpdate
state = {}


componentDidMount() {
}

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

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

export default App;




이렇게 수정하고 나면 Loading 이라는 메세지만 출력 될 것 입니다.

인터넷창에서 요소검사 network 탭에 가서 자세히 살펴보겠습니다.

localhost를 불렀고 , 리액트를 부르고(bundel.js), 그리고 데이터베이스를 불렀습니다. API 말입니다.

이렇게 ajax + react는 쉽습니다.

다시 정리하면 컴포넌트가 mount 되면 저 url을 가서 fetch 해오는 것입니다.

원하는 어떤 URL도 불러올 수 있습니다. 구글 url도 가능합니다.

ajax는 url을 자바스크립트로 asynchronous(비동기화) 방법으로 불러오게 됩니다.

왜 사람들은 ajax를 쓸까요? 왜냐하면 무엇인가를 불러올 때마다 페이지 새로고침을 하고 싶지 않기 때문입니다.

예를 들면, 로딩을 하면 API를 불러오고, 동시에 평점을 가져오기도 하기도 합니다. 

그래서 ajax가 멋진 것입니다. 자바스크립트와 같이 데이터를 다룰 수 있기 때문입니다. 뒤에 숨어서 말입니다.

우리 프로젝트를 예로 들면 , 데이터를 가져오는 상황을 사용자가 볼 수 없습니다. URL창에도 말입니다.

새로고침 없이 작업이 가능하고 리액트와 작업이 간편하기 때문에 AJAX를 추천합니다.






모든 컴포넌트가 state가 있는 것은 아닙니다.

어떤 컴포넌트는 state가 없는 stateless functional 컴포넌트입니다.

이 두 개의 컴포넌트는 큰 차이점이 있습니다.

한 개는 state가 있고, 나머지 한 개는 state가 없고 필요하지도 않습니다.

state가 없고 props 밖에 없을 때는 클래스 컴포넌트를 쓰는 대신에 

그들을 stateless functional 컴포넌트로 바꾸면 됩니다.


일단 전에 작성했던 원본 Movie.js 입니다.

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import './Movie.css';

class Movie extends Component {
static propTypes = {
title: PropTypes.string.isRequired,
poster: PropTypes.string.isRequired
}

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

class MoviePoster extends Component {
static propTypes = {
poster: PropTypes.string.isRequired
}

render(){
return (
<img src={this.props.poster} alt="Movie Poster" />
)
}
}

export default Movie



예를 들어서 movie poster라는 클래스 컴포넌트를 쓰는 대신에 

이를 stateless functional 컴포넌트로 바꾸는 방법은 아래와 같습니다.


/*
class MoviePoster extends Component {
static propTypes = {
poster: PropTypes.string.isRequired
}

render(){
return (
<img src={this.props.poster} alt="Movie Poster" />
)
}
}
*/

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

)
}


초록색으로 주석 처리 된 부분이 원본 내용이고 그 밑에 쓰여진 내용으로 대체된 것입니다.

어떤 컴포넌트는 그냥 return 을 위해 존재합니다.

1개의 props만 있고, 1개의 html 태그만 필요합니다.

이렇게 functional 컴포넌트를 만드는 것입니다.

우리가 기억해야하는 규칙은 , 이들은 state가 없다는 것입니다.

저장하고 새로고침 해보시면 전과 동일하게 loading 출력 후 5초 뒤에 영화정보가 나타납니다.








오류는 없습니다. 그냥 functional 컴포넌트를 사용할 뿐입니다.

class 컴포넌트 대신에 말입니다.

그렇다면 어떻게 prop types를 확인할 수 있을까요?

아주 쉽습니다.

아래와 같이 간단하게 작성하시면 됩니다.


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

)
}

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



저장 후에 console 창을 확인해보겠습니다. 

에러가 발생하지 않은 것을 확인하실 수 있습니다.






string 대신에 number로 변경하면 어떻게 될까요? 에러가 발생될 것 같은데  맞는지 확인해보겠습니다.


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





예상한 대로 에러가 발생하였습니다. 이렇게 proptypes 확인하는 방법까지 작성해봤습니다.

이번에는 movie 컴포넌트를 functional 로 변경해보겠습니다.


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

/*
class Movie extends Component {
static propTypes = {
title: PropTypes.string.isRequired,
poster: PropTypes.string.isRequired
}

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

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



propTypes도 작성합니다.


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

}


주석처리된 부분과 비교해보면 state가 필요없을 때는 functional 이 더 간단하다는 것을 알게 되었습니다.

하지만 state가 없기 때문에 업데이트 같은 멋진 것들은 할 수가 없어지는 것입니다.

이번시간에는 여기까지 알아보겠습니다. 고맙습니다.


이번 시간에는 loading states에 대해서 이야기 해보도록 하겠습니다.

여러분이 필요한 데이터가 항상 바로 즉시 존재하지는 않을 겁니다.

데이터 없이 컴포넌트가 로딩을 하고, 데이터를 위해 api를 불러서, 

api가 데이터를 주면, 여러분의 컴포넌트 state를 업데이트 할것입니다. 

api콜을 타임아웃 기능으로 유사하게 구현해보겠습니다.

이를 위해서 영화리스트를 funtion으로 이동하겠습니다.

전 시간까지 코딩한 app.js 파일은 아래와 같습니다.


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




class App extends Component {
//Render : componentWillMount() -> render() -> componentDidMount()
//Update : componentWillReceiveProps() -> shouldComponentUpdate() -> componentwillUpdate() -> render() -> componentDidUpdate
state = {
greeting: 'Hello React왕초보',
movies : [
{
title : "Matrix",
},
{
title : "Full Metal Jacket",
},
{
title : "Oldboy",
},
{
title : "Star Wars",
}
]
}

componentDidMount(){
setTimeout(() => {
this.setState({
movies: [
{
title: "신과함께",
},
...this.state.movies
]
});
}, 5000)
}

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

export default App;




아래와 같이 코딩을 해주고 새로고침을 하니 에러가 발생하였습니다.


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




class App extends Component {
//Render : componentWillMount() -> render() -> componentDidMount()
//Update : componentWillReceiveProps() -> shouldComponentUpdate() -> componentwillUpdate() -> render() -> componentDidUpdate
state = {
}

componentDidMount(){
setTimeout(() => {
this.setState({
movies: [
{
title: "Matrix",
poster:
},
{
title: "Full Metal Jacket",
poster:
},
{
title: "Oldboy",
poster:
},
{
title: "Star Wars",
poster:
}
]
});
}, 5000)
}

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

export default App;





×
This screen is visible only in development. It will not appear if the app crashes in production.
Open your browser’s developer console to further inspect this error.



state.movies가 존재하지 않는다고 에러가 발생한 것입니다.


{this.state.movies.map((movie, index) => {


map을 돌리려고 하는데 states.movies가 없으므로 에러가 발생하였습니다.

여기서 loading states가 필요합니다.

간단하게 loading 이라고 작성해보겠습니다.


render() {
return (
<div className="App">
Loading
</div>
);
}
}


새로고침해서 확인해보면 에러가 발생하지 않고 Loading이라고 뜰겁니다.

영화가 state에 없을 때마다 로딩을 띄우거나 , 영화리스트를 보이도록 하겠습니다.

영화리스트를 불러오는 function을 만들겠습니다.

이름은 render movies 이고, 아래와 같이 작성하겠습니다.


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


위에서 작성한 내용은 movies라는 variable에 데이터를 저장한 것입니다.

그리고 데이터가 없을때 '로딩'을 띄우고, 있으면 영화정보가 보이도록 하려면 다음과 같이 작업하면 됩니다.


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

{this.state.movies ? 라는 부분은 데이터가 있는지 물어보는 것입니다.


데이터가 있으면 movies 를 render하라는 내용입니다. 만약 데이터가 없으면 Loading 을 출력하라는 것입니다.


_renderMovies()

 위에서 _(언더바)를 사용한 이유는 리액트 자체 기능이 많기 때문에 

리액트 자체 기능과 여러분의 기능에 차이를 두기 위해서입니다.

이제 새로고침을 해서 인터넷화면을 확인해보겠습니다.

새로고침을 하면 아래와 같이 Loading이 먼저 출력되고 5초후에 영화 이미지와 함께 리스트가 출력됩니다.






여러분들도 여기까지 잘 출력되었을 것이라고 생각됩니다. 이번시간은 여기까지 하도록 하겠습니다.

고맙습니다.
















이번 시간에는 state를 조금 더 연습해 보겠습니다.

컴포넌트 외부에 있는 무비 리스트를 state 안으로 옮겨보겠습니다.

class App extends Component {
//Render : componentWillMount() -> render() -> componentDidMount()
//Update : componentWillReceiveProps() -> shouldComponentUpdate() -> componentwillUpdate() -> render() -> componentDidUpdate
state = {
greeting: 'Hello React왕초보',
movies : [
{
title : "Matrix",
},
{
title : "Full Metal Jacket",
},
{
title : "Oldboy",
},
{
title : "Star Wars",
}
]
}

위와 같이 수정하자 아래와 같이 movies가 정의되지 않았다는 에러가 발생하였습니다.

Failed to compile.

./src/App.js
  Line 45:  'movies' is not defined  no-undef

Search for the keywords to learn more about each error.

app.js 의 아래 render 부분을 아래와 같이 수정하여주니 에러가 발생하지 않습니다.

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


여기에 영화 리스트에서 영화를 한 개 더 추가하고 싶을 때는 어떻게 해야 할까요?

일단 아래와 같이 comoponetDidMount 부분을 추가해봅시다.


componentDidMount(){
setTimeout(function(){
console.log('hello react 왕초보')
}, 1000)
}


1초 후에 console에서 아래와 같이 출력되는 것을 확인하실 수 있습니다.




이렇게 00초 후에 페이지 로드 후 , 어떤 작업을 수행시킬 수 있습니다.

하지만 위에 작성한 것은 예전 자바스크립트 문법입니다. function을 다 작성하지 않아도 됩니다.

아래와 같이 최신 자바스크립트 문법으로 가능합니다.

componentDidMount(){
setTimeout(() => {
console.log('hello react 왕초보')
}, 1000)
}


이제 진짜 영화 리스트를 추가해 보겠습니다.

componentDidMount(){
setTimeout(() => {
this.setState({
movies: [
...this.state.movies,
{
title: "신과함께",
}
]
});
}, 1000)
}

영화 제목  "신과함께" 을 추가해주었습니다. 아래와 같이 잘 출력되었습니다.




위에 코딩한 내용을 자세히 살펴보겠습니다.

컴포넌트가 mount하면 페이지 로드 후 1초 후에 새로운영화가 보여지게 되는 것입니다.

영화리스트에 영화를 추가하는 것입니다. 그런데 아래와 같이 해당코드를 삭제하게 되면

1초 후에 모든 영화가 사라지고 방금 추가한 영화만 남게 됩니다. 


...this.state.movies, 이 부분은 "이전 영화 리스트를 그대로 두고, 

그리고 한 개 영화를 추가"하라는 뜻입니다. 

이와 같이 state를 활용해서 응용을 하면 다양한 효과를 볼 수 있습니다.

예를 들면, 페이지를 로딩할 때 스크롤을 아래로 내릴수록 

더 많은 영화가 로딩되는 효과(infinite scroll)도 구현할 수 있습니다.

끝까지 스크롤하면 영화를 추가로 로딩해주는 것입니다. 페이스북에서도 이런 방식을 사용하고 있습니다. 

인스타그램도 동일합니다.


그럼 이제 아까 작성한 스크립트를 약간 변경해보겠습니다.

this.state.movies 부분 위치를 변경해보겠습니다. 시간도 5초로 변경하였습니다.

어떻게 될까요?

componentDidMount(){
setTimeout(() => {
this.setState({
movies: [
{
title: "신과함께",
},
...this.state.movies
]
});
}, 5000)
}






위에 캡쳐한 그대로 "신과함께"가 제일 위로 변경된 것을 확인하실 수 있습니다. 

오늘은 여기까지 하도록 하겠습니다. 고맙습니다.






이번 강의에서 배울 것은 state 입니다.

state는 리액트 컴포넌트 안에 있는 오브젝트입니다.

컴포넌트 안에 state가 바뀔 때마다, 컴포넌트는 다시 render 할 겁니다.

state를 작성해보겠습니다.

class App extends Component {
//Render : componentWillMount() -> render() -> componentDidMount()
//Update : componentWillReceiveProps() -> shouldComponentUpdate() -> componentwillUpdate() -> render() -> componentDidUpdate
state = {
greeting: 'Hello React왕초보'
}

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




컴포넌트가 mount되면 5초를 기다리고, greeting을 업데이트 할 겁니다.

그렇게 하기 위해서 setTimeout를 작성하겠습니다. 

state는 직접적으로 쓰면 안됩니다. 그렇기 때문에 this.setState 를 사용할 겁니다.

아래 코딩의 뜻은 Hello React왕초보를 출력하고 5초뒤에 Hello again으로 변경하라는 뜻입니다.


state = {
greeting: 'Hello React왕초보'
}

componentDidMount(){
setTimeout(() => {
this.setState({
greeting: 'Hello again!'
})
}, 5000)
}


저장하고 처음 출력되는 내용을 확인하시고 , 5초뒤에 잘 변경 되는지 확인해보시기 바랍니다. 







아래에 있는 state는 컴포넌트를 로드하는 방법입니다(디폴트 state와 함께 로드 됩니다.)

state = {
greeting: 'Hello React왕초보'
}


아래에 컴포넌트가 did mount한 후에는 5초 후에 hello again 으로 변경되도록 코딩을 한 것입니다.


componentDidMount(){
setTimeout(() => {
this.setState({
greeting: 'Hello again!'
})
}, 5000)
}


꼭 기억해야 할 것은 state를 바꿀 때는 setState를 설정하고, 

업데이트 할 때마다 새로운 state와 함께 render가 작동된다는 것입니다. 

코딩한 내용은 적었지만 state는 리액트의 중요한 문법 중에 하나입니다.

이후 시간에 state를 실전에서 쓰면서 이해가 더 잘 되실겁니다.

고맙습니다.








 



이번 시간에는 리액트의 컴포넌트 라이프싸이클에 대해서 배워 보겠습니다.

컴포넌트는 여러 기능들을 정해진 순서대로 실행합니다. 그것에 대해서 배워보겠습니다.

render를 할 때(컴포넌트를 띄울 때) 이 순서대로 실행될겁니다.


//Render : componentWillMount() -> render() -> componentDidMount()

첫번째는 componentWillMount
두번째는 render
세번째는 componentDidMount

이 싸이클은 자동으로 발생합니다. 

console.log 를 통해서 위 순서대로 실행되는지 확인해보겠습니다.


class App extends Component {
//Render : componentWillMount() -> render() -> componentDidMount()

//Update : componentWillReceiveProps() -> shouldComponentUpdate() -> componentwillUpdate() -> render() -> componentDidUpdate
componentWillMount() {
console.log('will mount')
}

componentDidMount(){
console.log("did mount");

}

render() {
console.log("did render");

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

export default App;


저장을 하고 크롬창에서 검사를 통해 확인해 보겠습니다. 아래와 같이 순서대로 출력되는 것을 확인하였습니다.




App.js:32 will mount
App.js:41 did render
App.js:36 did mount


예를 들어 , 영화앱과 같은 어플리케이션을 만든다면,  will mount를 진행할때 api에 작업을 요청할 겁니다.

해당 작업 수행이 완료되면, 그 다음에 did mountf 부분에서 데이터 관련된 작업을 하게 될겁니다.

이 싸이클을 알아두는게 유용한 이유는,  컴포넌트를 만드는데 도움이 되기 때문입니다.

will mount를 보면 사이클이 시작되었음을 알게될거고, 

render를 보면 이제 컴포넌트가 리액트에 존재하게 되었음을 알게 될겁니다.

did mount를 보면, 이제 성공적으로 리액트 컴포넌트가 자리잡았음을 알게 되는 것입니다.

update 조금 더 스텝이 많습니다.

첫번째는   componentWillReceiveProps 이건 컴포넌트가 새로운 props를 받았다는 뜻입니다.
두번째는   shouldComponentUpdate 리액트는 old props, 새로운 props를 살펴본 다음에, 
               이전과 새로운 prop가 다르면,  shouldComponentUpdate() = true 라고 생각할 겁니다. 
세번째는    componentwillUpdate
네번째는    render
다섯번째는 componentDidUpdate

이 순서를 잘 이해하면 나중에 컴포넌트를 빌드할 때 도움이 될 것입니다.

예를 들어 , component will update 수행할 때 
                 어플리케이션에 뱅글뱅글 돌아가는 spinner를 붙일 수 있을 겁니다.

업데이트 이 후에는, 돌고 있던 '로딩 중 ' 메세지나 아이콘을 숨기면 될 겁니다.

이론의 세부적인 것은 다루지 않을 것이고 앞으로 실전으로 바로 해보면서 배우게 될 예정입니다.


더 자세한 라이프사이클 관련 링크는 아래를 참고하세요. 고맙습니다.

+ Recent posts