[React] 기초 - movie app 만들기

Nam Eun-Ji·2020년 11월 27일
0

React

  • UI library
  • 리액트는 JSX라는 문법을 사용하여 레이아웃을 컴포넌트라는 작은 단위로 나누어서 코딩하는데 이는 코드의 재사용성을 높여준다.
  • 작성한 코드를 컴파일하는 과정을 꼭 거쳐야한다.
  • 리액트는 데이터의 흐름이 아래로만 이루어지기 때문에 부모의 데이터를 바꾸기 위해서는 state를 이용해야 한다.
  • JSX : 예를 들어 HTML에서는 class이던게 JSX에서는 className으로 대체된다. 또한 명령을 실행시키려면 괄호를 꼭 쳐야한다.

Component 컴포넌트

  • 모든 컴포넌트는 render function을 가지고 있다.
  • render : ‘뭔가를 보여주는, 출력하는’ 기능 / ‘이 컴포넌트가 나에게 보여주는 것이 무엇인가’
  • 컴포넌트는 항상 render를 해줘야한다.

props

  • 부모컴포넌트에서 자식컴포넌트로 전달해주는 데이터
  • 자식컴포넌트에서는 데이터를 바꿀 수 없고 오직 read만 가능하다.
  • 바꾸고 싶다면 데이터를 전달해준 최상위 부모컴포넌트만 가능하다.

state

  • state : 리액트 컴포넌트안에 있는 오브젝트
  • state가 바뀔 때마다 컴포넌트는 다시 render된다.
  • 동적인 데이터를 다룰 때 사용한다.


React? ReactDOM? ReactNative?

  • React : UI 라이브러리(프론트엔드 라이브러리)
  • ReactDOM : 리액트롤 웹사이트에 출력(render)하는 걸 도와주는 모델
  • ReactNative : 리액트를 모바일앱에 출력(render)하는 걸 도와주는 모델
    즉 리액트 = 라이브러리, 리액트돔 = 라이브러리를 웹사이트에 출력해줌


설치방법

리액트에서는 create-react-app으로 작업환경을 셋팅할 수 있다.
npm start 이후에는 http://localhost:3000 에서 확인 가능하다.

npm install create-react-app 
create-react-app hello-world
cd hello-world
npm start


부모컴포넌트가 자식 컴포넌트에게 정보를 주는 형태

아래 코드를 보면 클래스 App컴포넌트(부모)가 클래스 Movie컴포넌트(자식)에게 정보를 전달하고 있다.
부모컴포넌트에서 movies 요소들을 title이라고 명시하였고 자식컴포넌트에서 console.log(this.props) 를 찍어보면 객체 형태로 전달되고 있다.

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

const movies = [
  "Matrix",
  "Full Metal Jacket",
  "Oldboy",
  "Star Wars"
]
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>
    )
  }
}

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

class Movie extends Component{
    render(){
        console.log(this.props);
        return(
            <div>
                <MoviePoster />
                <h1>{this.props.title}</h1>
            </div>
        )
    }
}
class MoviePoster extends Component{
    render(){
        return(
            <img src="https://img0.yna.co.kr/etc/inner/KR/2020/01/10/AKR20200110125500005_01_i_P4.jpg"></img>
        )
    }
}
export default Movie; 


list & map

JavaScript Array에는 map메소드를 제공한다. 이를 이용해 굳이 일일이 정보를 다 html내에 넣지 않아도 코드를 간결하고 보기 좋게 짤 수 있다.

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

const movies = [
  {
    title: "Matrix",
    poster: "https://img.hankyung.com/photo/201909/2019091713474280260-540x787.jpg"
  },
  {
    title: "Full Metal Jacket",
    poster: "https://m.media-amazon.com/images/M/MV5BNzkxODk0NjEtYjc4Mi00ZDI0LTgyYjEtYzc1NDkxY2YzYTgyXkEyXkFqcGdeQXVyNzkwMjQ5NzM@._V1_.jpg"
  },
  {
    title: "Oldboy",
    poster: "https://i.pinimg.com/originals/69/50/3a/69503a39b0e2e829b2bbd4ebeebe0727.jpg"
  },
  {
    title: "Star Wars",
    poster: "https://img.extmovie.com/files/attach/images/135/679/079/026/7f30f6a1396b05a58a27fb10ff21e511.jpg"
  }
]

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

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

class Movie extends Component{
    render(){
        return(
            <div>
                <MoviePoster poster={this.props.poster}/>
                <h1>{this.props.title}</h1>
            </div>
        )
    }
}
class MoviePoster extends Component{
    render(){
        return(
            <img src={this.props.poster}></img>
        )
    }
}
export default Movie; 


array에 있는 각 child는 반드시 고유한 key prop을 가져야한다.

즉 리액트는 element가 많을 경우 key라는 것을 줘야한다.
아래 코드에서 index란 movies를 map메소드를 이용해 돌면서 각각의 요소(movie)의 고유한 인덱스 번호로 key={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>
    )
  }
}


Validating Props with Prop Types

props에 내가 원하는 값을 출력하고 싶다면?
예를 들면 영화제목에는 string형식으로 와야하고, 포스터에 출력되는 값이 boolean형태가 아닌 string형태로 왔으면 좋겠다면?
마치 이 부분은 mysql에서 테이블을 생성할 때 데이터형을 지정해놓는 것과 유사하다.
이를 이용하면 부모컴포넌트에서 얻는 정보의 종류가 무엇인지, 있는지 없는지 알 수 있다.

일단 install

yarn add prop-types

아래와 같이 static propTypes을 지정해서 데이터형을 지정해준다.
만약 props.title에 number 형태가 온다면 에러가 날 것이다.

또한 isRequired를 명시해준다면 해당 데이터는 부모컴포넌트에서 필수로 받아와야하는 데이터가 된다.
만약 아래의 h1태그 안의 this.props.title을 제거해준다면 required의 필수조건인 prop이 없다고 콘솔에 warning 알림이 뜰 것이다.

// Movie.js
class Movie extends Component{

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

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


Component Lifecycle

리액트는 컴포넌트가 존재하기 시작한다면 자동으로 will mount → render → did mount 순서대로 실행한다. render와 update의 cycle의 순서는 다음과 같다.

  • render cycle

    1. componentWillMount → api에 작업 요청
    2. render → 위 작업이 완료되면 데이터 관련 작업 수행
    3. componentDidMount → 성공적으로 리액트 세계에 컴포넌트가 자리잡았음을 알 수 있음
  • update cycle

    1. componentWillReceiveProps → 컴포넌트가 새로운 props를 받음
    2. shouldComponentUpdate → old props, new props를 살펴본 후 이전과 새로운 props가 다르면 리액트는 ‘업데이트=true’라고 판단함. true일 경우 다음 단계로 넘어감
    3. componentWillUpdate
    4. render
    5. component


Component State

state를 바꿀 때에는 setState를 설정해서 바꿔줘야 하고, 업데이트할 때마다 새로운 state와 함께 render가 작동될 것이다.
예제1) 한번 render되어서 상단에 'Hello!'를 출력했다가 완료되면 greeting을 다시 설정해줘서 5초 후 다시 render해서 'Hello Again!'을 출력

// App.js
class App extends Component {
  
  state = {
    greeting: 'Hello!'
  }
  componentDidMount(){
    setTimeout(() => {
      this.setState({
        greeting: 'Hello Again!!'
      })
    }, 5000)
  }

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

}

예제2) 영화의 목록을 render한 후 완료되면 새로운 영화 하나를 밑에 추가
중간에 ...this.state.movies 를 명시해줬는데 이는 이전 영화를 그대로 두고 밑에 한개의 영화를 추가하겠다는 뜻이다. 만약 이 부분을 삭제한다면 movies를 다시 셋팅해주는 것이기 때문에 이전 영화목록은 삭제되고 추가한 영화만 남게 된다.

// App.js
componentDidMount(){
    // setTimeout : ~시간 후에 ~작업을 수행시킨다
    setTimeout(() => {
      this.setState({
        movies:[
          ...this.state.movies,
          {
            title: "Traninspotting",
            poster: "https://www.barakashop.co.za/media/catalog/product/cache/1/image/650x/040ec09b1e35df139433887a97daa66f/t/r/trainspotting_orange_5characters_1.jpg"
          }
        ]
      })
    }, 3000)
  }

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

위의 예제를 보며 생각해보면 render가 끝나고 componentDidMount에서 다시 setState를 해주면 다시 render가 되는데 이 때 기존의 영화리스트도 다시 출력해줘야하는 것은 꽤나 비효율적이다라고 생각된다. 하지만 가장 확실한 방법이라고 하니...



loading states

예제) 데이터가 있는지 체크하고 없으면 'Loading'이란 텍스트를 출력하고, 있으면 _renderMovies 함수를 실행하여 데이터를 출력한다.

class App extends Component {
  
  state = {}
  componentDidMount(){
    // setTimeout : ~시간 후에 ~작업을 수행시킨다
    setTimeout(() => {
      this.setState({
        movies : [
          {
            title: "Matrix",
            poster: "https://img.hankyung.com/photo/201909/2019091713474280260-540x787.jpg"
          },
          {
            title: "Full Metal Jacket",
            poster: "https://m.media-amazon.com/images/M/MV5BNzkxODk0NjEtYjc4Mi00ZDI0LTgyYjEtYzc1NDkxY2YzYTgyXkEyXkFqcGdeQXVyNzkwMjQ5NzM@._V1_.jpg"
          },
          {
            title: "Oldboy",
            poster: "https://i.pinimg.com/originals/69/50/3a/69503a39b0e2e829b2bbd4ebeebe0727.jpg"
          },
          {
            title: "Star Wars",
            poster: "https://img.extmovie.com/files/attach/images/135/679/079/026/7f30f6a1396b05a58a27fb10ff21e511.jpg"
          },
          {
            title: "Traninspotting",
            poster: "https://www.barakashop.co.za/media/catalog/product/cache/1/image/650x/040ec09b1e35df139433887a97daa66f/t/r/trainspotting_orange_5characters_1.jpg"
          }
        ]
      })
    }, 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>
    )
  }

}  


Smart vs Dumb Component

  1. Smart : state component (class component)
// state component
class MoviePoster extends Component{
    static propTypes = {
        poster: PropTypes.string.isRequired
    }
    render(){
        return(
            <img src={this.props.poster} alt=""/>
        )
    }
}
  1. Dumb : stateless component (functional component)
  • 클래스 컴포넌트를 쓰는 대신에 function으로 바꿔주면 된다.
  • 이 컴포넌트는 function render, cycle도 없고 오로지 return만 있다.
  • 만약 단순히 업데이트 없이 html만 return해야한다면 stateless component를 써도 무방하다.
// stateless component
function MoviePoster({poster}){
    return (
        <img src={poster} alt=""/>
    )
}
// prop type 확인 방법
MoviePoster.propTypes = {
    poster: PropTypes.string.isRequired
} 


AJAX on React

  • Asynchronous JavaScript and XM
  • AJAX는 url을 자바스크립트로 asynchronous(비동기)방법으로 불러온다.
  • JSON : 오브젝트를 자바스크립트로 작성하는 기법
  • 컴포넌트가 mount되면 해당 url로 가서 fetch해온다.
componentDidMount(){
    fetch('https://yts.mx/api/v2/list_movies.json')
}


Promise

  • promise : 자바스크립트 비동기 처리에 사용되는 객체
  • 비동기란? 특정 코드의 실행이 완료될 때까지 기다리지 않고 다음 코드를 먼저 수행하는 자바스크립트의 특성
  • then function은 한 개의 attribute만 주고 그것은 fetch의 결과물이자 object 형태이다.
  • fetch 후 성공과는 상관없이 작업이 완료되면 then을 수행하고, 만약 에러가 난다면 catch를 실행
componentDidMount(){
    fetch('https://yts.mx/api/v2/list_movies.json?sort_by=rating')
    .then(response => console.log(response)) // 괄호 안에는 작업내용
    .catch(err => console.log(err))
}
profile
한 줄 소개가 자연스러워지는 그날까지

0개의 댓글