목차
1. props 다시 정리

2. props 기본값 설정 : defaultProps

3. 태그 사이의 내용을 보여주는 children

4. 비구조화 할당 문법을 통한 props 내부 값 추출

5. proTypes를 통한 props 검증

6. state

7. this.setState에 객체 대신 함수 인자 전달-+

8. 함수컴포넌트에서 useState로 state 사용


1. props

앞에서도 말했지만 props는 properties를 줄인 표현으로 컴포넌트 속성을 설정할 때 사용하는 요소이다.

 

아래와 같은 구조일 경우 App Component가 부모 컴포넌트이고 MyComponent Component가 자식 컴포넌트이다.

props는 부모 컴포넌트에서 설정할 수 있다.

//App 컴포넌트
import MyComponent from "./MyComponent"; 

const App=()=> {
  return (
    <div>
     <MyComponent name="홍길동" />
    </div>
  );
}

export default App;
//MyComponent 컴포넌트
const MyComponent=(props)=>{

    return <div>안녕하세요, 제 이름은 {props.name}입니다.</div>;

}

export default MyComponent;

위와 같이 부모 컴포넌트에 name 속성을 홍길동이라고 줬더니 자식 컴포넌트에 props.name 자리에 홍길동이 잘 나타나게 된다.

이런식으로 name이든 다른 속성이든 props를 다르게 주면 다른 element(다른 맛 붕어빵)이 만들어진다.


2. props 기본값 설정 : defaultProps

//App 컴포넌트
import MyComponent from "./MyComponent"; 

const App=()=> {
  return (
    <div>
     <MyComponent />
    </div>
  );
}

export default App;

부모컴포넌트에서 속성값을 안주게 되면 아래와 같이 렌더링되는데

이렇게 props값을 안받았을 경우에 default(기본값)으로 어떤 이름이 나오게 설정할 수 있다.

그게 바로 defaultProps이다

//MyComponent 컴포넌트

const MyComponent=(props)=>{

    return <div>안녕하세요, 제 이름은 {props.name}입니다.</div>;

}

MyComponent.defaultProps={
    name: '이름없음'
}

export default MyComponent;


3. 태그 사이의 내용을 보여주는 children

리액트 컴포넌트를 사용할 때 컴포넌트 태그 사이의 내용을 보여주는 props를 children이라고 한다.

//MyComponent 컴포넌트

const MyComponent=(props)=>{

    return <div>안녕하세요, 제 이름은 {props.children}입니다.</div>;

}

export default MyComponent;
//App 컴포넌트
import MyComponent from "./MyComponent"; 

const App=()=> {
  return (
    <div>
     <MyComponent >애기</MyComponent>    
    </div>
  );
}

export default App;


4. 비구조화 할당 문법을 통한 props 내부 값 추출

현재 MyComponent에서 props 값을 조회할 때마다 props.name, props.children과 같이 props라는 키워드를 앞에 붙여 주고 있는데 이러한 작업을 편하게 하기위해 비구조화 할당 문법을 사용할 수 있다.

 

▶구조 분해 할당

배열이나 객체의 속성을 해체하여 그 값을 개별 변수에 담을 수 있게 하는 JavaScript 표현식이다.

 

그 중에서 객체 구조 분해할당의 형태는 다음과 같다

var o = {p: 42, q: true};
var {p, q} = o;

위와 같이 선언해줌으로써 p와 q를 개별변수로 사용할 수 있게 된다.

p만 호출해도 42라는 값을 가져올 수 있다.

 

리액트에서도 마찬가지이다.

const{name, children}=props라고 해줌으로써

props.name, props.children이라고 명시를 하지 않고 그냥 name, children이라고만 해줘도 해당 값에 접근이 가능해진 것이다.

//MyComponent 컴포넌트

const MyComponent=(props)=>{
    const{name, children}=props;
    return <div>안녕하세요, 제 이름은 {name}이자 {children}입니다.</div>;

}

export default MyComponent;

또는

//MyComponent 컴포넌트

const MyComponent=({name, children})=>{
    return <div>안녕하세요, 제 이름은 {name}이자 {children}입니다.</div>;

}

export default MyComponent;
//App 컴포넌트
import MyComponent from "./MyComponent"; 

const App=()=> {
  return (
    <div>
     <MyComponent name="길동">애기</MyComponent>    
    </div>
  );
}

export default App;

5. proTypes를 통한 props 검증

컴포넌트의 필수 props를 지정하거나 props의 타입(type)을 지정할 때는 proTypes를 사용한다.

proTypes를 사용하려면 코드 상단에 import 구문을 사용하여 불러와야 한다.

import ProTypes from 'prop-types';

 

아래와 같이 name 속성을 proTypes로 string으로 지정해놓으면 문자열이 아닌 다른 자료형일 경우 Console창에 에러메시지가 뜬다.

//MyComponent 컴포넌트
import ProTypes from 'prop-types';

const MyComponent=(props)=>{
    const{name, children}=props;
    return <div>안녕하세요, 제 이름은 {name}이자 {children}입니다.</div>;

}

MyComponent.proTypes={
    name:ProTypes.string
}

export default MyComponent;

 

▶isRequired를 사용하여 필수 proTypes 설정

필수로 입력돼야 하는 proType에 isRequired를 붙여준다.

//MyComponent 컴포넌트
import ProTypes from 'prop-types';

const MyComponent=(props)=>{
    const{name, children}=props;
    return <div>안녕하세요, 제 이름은 {name}이자 {children}입니다.</div>;

}

MyComponent.proTypes={
    name:ProTypes.string.isrequired
}

export default MyComponent;

 

▶클래스형 컴포넌트에서 props 사용

클래스형 컴포넌트에서 props를 사용할 때 render 함수에서 this.props를 조회하면 되며 defaultProps와 propTypes는 똑같은 방식으로 설정가능하다.

//MyComponent 컴포넌트
import {Component} from 'react';
import ProTypes from 'prop-types';

class MyComponent extends Component{
    render(){
    const {name, children}=this.props;

     return(<div>안녕하세요, 제 이름은 {name}이자 {children}입니다.</div>)};

}

MyComponent.proTypes={
    name:ProTypes.string.isrequired
}

export default MyComponent;

6. state

state는 컴포넌트 내부에서 바뀔 수 있는 값을 의미한다.

props는 부모 컴포넌트가 설정하는 값이며 컴포넌트 자신을 해당 props를 읽기 전용으로만 사용할 수 있고 props를 변경하려면 부모 컴포넌트에서 바꿔줘야하지만 state는 내부에서 바뀌게 만들 수 있는 것이다.

 

바뀔수 있는 state 초기값을 설정해주는 형태는 아래와 같다.

컴포넌트의 state는 객체 형식이어야 한다.

 

초기값을 설정해주는 방법은 constructor에 설정해주거나 constructor을 사용하지 않고 바로 state를 설정해주는 방법 2가지가 있다.

 this.state = {
      number: 0,
    };

 

클래스형 컴포넌트에서 constructor를 작성할 때는 반드시 super(props)를 호출해 주어야 하며 

이 함수가 호출되면 현재 클래스형 컴포넌트가 상속받고 있는 리액트의 Component 클래스가 지닌 생성자 함수를 호출해준다.

 

아래에 나오는 onClick이라는 이벤트는 클릭하면 onClick 다음에 오는 함수를 실행하라는 의미이며 이 함수를 넣어줄 때는 화살표 함수 문법을 사용하여 넣어줘야 한다.

 

함수 내부에서 this.setState라는 함수를 사용하는데 이 함수가 state 값을 바꿀 수 있게 해 준다.

 

//생성자에서 state 초기값 설정

import { Component } from 'react';

class Counter extends Component {
  constructor(props) {
    super(props);
    this.state = {
      number: 0,
    };
  }
  render() {
    const { number } = this.state;
    return (
      <div>
        <h1>{number}</h1>
        <button
          onClick={() => {
            this.setState({ number: number + 1 });
          }}
        >
          +1
        </button>
      </div>
    );
  }
}

export default Counter;

 

//생성자 없이 state 초기값 설정

import { Component } from 'react';

class Counter extends Component {
    state = {
      number: 0,
    };
  
  render() {
    const { number } = this.state;
    return (
      <div>
        <h1>{number}</h1>
        <button
          onClick={() => {
            this.setState({ number: number + 1 });
          }}
        >
          +1
        </button>
      </div>
    );
  }
}

export default Counter;

state값에는 여러 값이 올 수 있다.

    state = {
      number: 0,
      fixNumber:0
    };

7. this.setState에 객체 대신 함수 인자 전달

아래 예제코드를 보면 onClick을 했을 대 setState가 2번 호출되도록 만들었다. 즉 +2씩 증가되도록 만들려고 한 것이다.

import { Component } from 'react';

class Counter extends Component {
    state = {
      number: 0,
    };
  
  render() {
    const { number } = this.state;
    return (
      <div>
        <h1>{number}</h1>
        <button
          onClick={() => {
            this.setState({ number: number + 1 });
            this.setState({ number: this.state.number + 1 });
          }}
        >
          +1
        </button>
      </div>
    );
  }
}

export default Counter;

그런데 실제 결과는 +1씩 증가되었다.

위와 같이 작성하면 this.setState를 2번 사용하는 것임에도 불구하고 1씩 더해지는데 this.setState를 사용한다고 해서 stae 값이 바로 바뀌는 것이 아니기 때문이다.

이런 문제점을 해결하기 위해서는 this.setState를 사용할 때 객체 대신 함수를 인자로 넣어주면 된다.

위 코드를 아래코드처럼 바꿔주면 된다.

import { Component } from 'react';

class Counter extends Component {
    state = {
      number: 0,
    };
  
  render() {
    const { number } = this.state;
    return (
      <div>
        <h1>{number}</h1>
        <button
          onClick={() => {
            this.setState({ number: number + 1 });
            this.setState(prevState=>{return {number:prevState.number+1}});
          }}
        >
          +1
        </button>
      </div>
    );
  }
}

export default Counter;

함수형태로 표현하는 방식은 2개가 있다.

prevState는 기존 상태를 의미하며 기존 상태의 number에 1을 더해주는 것이다.

 

이렇게 함수를 사용하면 즉시 반영이 돼서 위에 있는 코드는 +2씩 잘 증가되는 것을 볼 수 있다.

//1번째 방식
this.setState(prevState=>{return {number:prevState.number+1}}

//2번째 방식
this.setState(prevState=>({number:prevState.number+1}));

2번째 방식은 return을 쓰지 않고 바로 객체를 반환하는 것인데 이 방식을 쓰기 위해서는

function A(){}이런 형태일때 A()뒤에 나오는 중괄호(코드블럭)을 생략하고 써주며

객체를 반환하는 형태이므로 ( { } ) 객체를 나타내는 중괄호를 괄호로 감싸는 형태가 된다.

 

setState 작업이 끝난 후 또 다른 작업을 하고싶을 대는 setState의 두 번째 파라미터로 콜백함수를 등록하면 된다.

import { Component } from 'react';

class Counter extends Component {
    state = {
      number: 0,
    };
  
  render() {
    const { number } = this.state;
    return (
      <div>
        <h1>{number}</h1>
        <button
          onClick={() => {
            this.setState({ number: number + 1 },()=>{console.log("안녕")});
          }}
        >
          +1
        </button>
      </div>
    );
  }
}

export default Counter;


8. 함수컴포넌트에서 useState로 state 사용

과거에는 함수컴포넌트에서 state를 사용할 수 없었지만 지금은 useState라는 함수를 사용해 함수 컴포넌트에서도 state를 사용할 수 있다.

 

userState를 알아보기 전에 배열 비구조화 할당에 대해 먼저 알아본다.

 

▶배열 비구조화 할당

구조 분해 할당은 구문은 배열이나 객체의 속성을 해체하여 그 값을 개별 변수에 담을 수 있게 하는 JavaScript 표현식이다.

아래와 같이 red, yellow, green이라는 개별 변수로 배열 값을 접근할 수 있는 것이다.

▶useState사용

아래의 형태는 state 초기값을 ''으로 넣어준 것이다.

배열 첫번째 원소인 message는 현재 상태를 나타내고

배열 두번째 원소인 setMessage는 상태를 바꿔주는 함수를 나타내며 세터함수라고 부른다.

    const [message,setMessage]=useState('');
import {useState} from 'react';

const Say=()=>{
    const [message,setMessage]=useState(''); //state값을 ''으로 초기화
    const onClickEnter=()=>setMessage('안녕하세요!'); //state 값을 안녕하세요로 변경
    const onClickLeave=()=>setMessage('안녕히 가세요!');
    return (<div>
        <button onClick={onClickEnter}>입장</button> {/*클릭하면 onClickEnter함수 호출*/}
        <button onClick={onClickLeave}>퇴장</button>
        <h1>{message}</h1>
    </div>);
}

export default Say;

▶useState여러번 사용

 

useState는 한 컴포넌트에 여러번 사용해도 상관없다.

다만 style을 usestate로 지정할 때는 배열의 첫번째 원소인 현재 상태를 CSS에 사용되는 키워드로 사용해야 하며 원래 CSS에서 font-size였던 것이 리액트 에서는 fontSize라고 표현해줘야한다. (카멜 표기+ -기호 제거)

import {useState} from 'react';

const Say=()=>{
    const [message,setMessage]=useState('');
    const onClickEnter=()=>setMessage('안녕하세요!');
    const onClickLeave=()=>setMessage('안녕히 가세요!');

    const [color,setColor]=useState('black');
    const [fontSize,setfontsize]=useState('');
    return (<div>
        <button onClick={onClickEnter}>입장</button>
        <button onClick={onClickLeave}>퇴장</button>
        <h1 style={{color,fontSize}}>{message}</h1>
        <button style={{color:'red'}} onClick={()=>setColor('red')}>빨간색</button>
        <button style={{color:'green'}} onClick={()=>setColor('green')}>초록색</button>
        <button style={{color:'blue'}} onClick={()=>setColor('blue')}>파란색</button>
        
        <button onClick={()=>setfontsize('50px')}>폰트증가</button>
        <button onClick={()=>setfontsize('20px')}>폰트감소</button>

    </div>);
}

export default Say;

 

props를 사용한다고 해서 값이 무조건 고정적이지는 않다. 부모 컴포넌트의 staste를 자식 컴포넌트의 props로 전달하고, 자식 컴포넌트에서 특정 이벤트가 발생할 때 부모 컴포넌트의 메서드를 호출하며 props도 유동적으로 사용할 수 있다.


출처

리액트를 다루는 기술- 김민준

 

*이 게시글은 Do it! 클론 코딩 영화 평점 웹서비스를 보고 정리한 글입니다.


목차

1. HTML구조 만들어주고 장르 추가하기

2. CSS로 스타일링하기

3. 영화앱 여러 기능 추가하기-react-router-dom 설치하고 프로젝트 폴더 정리하기

4.  영화앱 여러 기능 추가하기-라우터 만들기

5. 영화앱 여러 기능 추가하기-내비게이션 만들어보기

6. 영화앱 여러 기능 추가하기-영화 상세 정보 기능 만들어보기

7. 영화앱 여러 기능 추가하기-리다이렉트 기능 만들어 보기

8. 영화 앱 깃허브에 배포하기


1. HTML 구조 만들어주고 장르 추가하기

그 다음 기본적인 스타일링을 해보도록 하자.

스타일링의 핵심은 CSS지만 CSS를 적용할 HTML도 필요하므로 HTML구조를 넣어준다.

 

HTML에서 class속성을 사용하려면 'class'를 그대로 쓰지만 리액트에서는 class 속성을 사용하기 위해서는 'class'가 아니라 'className'을 써야한다.

리액트는 JSX를 HTML로 변환하면서 className을 class로 다시 바꿔준다.

 

App.js

import React from 'react';
import axios from 'axios';
import Movie from './Movie';


class App extends React.Component {
  state = {
    isLoading: true,
    movies: [],
  };
  getMovies = async () => {
    const {
      data: {
        data: { movies },
      },
    } = await axios.get('https://yts-proxy.now.sh/list_movies.json?sort_by=rating');
    this.setState({ movies, isLoading: false });
  };
  componentDidMount() {
    this.getMovies();
  }
  render() {
    const { isLoading, movies } = this.state;
    return (
      <section className="container">
        {isLoading ? (
          <div className="loader">
            <span className="loader__text">Loading...</span>
          </div>
        ) : (
          <div className="movies">
            {movies.map((movie) => {
              return (
                <Movie
                  key={movie.id}
                  id={movie.id}
                  year={movie.year}
                  title={movie.title}
                  summary={movie.summary}
                  poster={movie.medium_cover_image}
                />
              );
            })}
          </div>
        )}
      </section>
    );
  }
}

export default App;

 

Movie.js

코드를 완성해보니 id props가 필요없어서 지운다.

import React from 'react';
import PropTypes from 'prop-types';

function Movie({ title, year, summary, poster }) {
  return (
    <div className="movie">
      <img src={poster} alt={title} title={title} />
      <div className="movie__data">
        <h3 className="movie__title">{title}</h3>
        <h5 className="movie__year">{year}</h5>
        <p className="movie__summary">{summary}</p>
      </div>
    </div>
  );
}

Movie.propTypes = {

  year: PropTypes.number.isRequired,
  title: PropTypes.string.isRequired,
  summary: PropTypes.string.isRequired,
  poster: PropTypes.string.isRequired,
};

export default Movie;

 

여기까지 만들고보니 영화에서 나름 중요한 장르 데이터가 안들어가있다.

추가해준다.

 

arrayOf(PropTypes.string)은 문자열을 원소로 하는 배열을 의미한다.

import React from 'react';
import PropTypes from 'prop-types';


function Movie({ title, year, summary, poster, genres }) {
  return (
    <div className="movie">
      <img src={poster} alt={title} title={title} />
      <div className="movie__data">
        <h3 className="movie__title">{title}</h3>
        <h5 className="movie__year">{year}</h5>
        <p className="movie__summary">{summary}</p>
      </div>
    </div>
  );
}

Movie.propTypes = {
  year: PropTypes.number.isRequired,
  title: PropTypes.string.isRequired,
  summary: PropTypes.string.isRequired,
  poster: PropTypes.string.isRequired,
  genres:PropTypes.arrayOf(PropTypes.string).isRequired,
};

export default Movie;

아래 App 컴포넌트에는

genres={movie.genres}를 추가한다.

import React from 'react';
import axios from 'axios';
import Movie from './Movie';


class App extends React.Component {
  state = {
    isLoading: true,
    movies: [],
  };
  getMovies = async () => {
    const {
      data: {
        data: { movies },
      },
    } = await axios.get('https://yts-proxy.now.sh/list_movies.json?sort_by=rating');
    this.setState({ movies, isLoading: false });
  };
  componentDidMount() {
    this.getMovies();
  }
  render() {
    const { isLoading, movies } = this.state;
    return (
      <section className="container">
        {isLoading ? (
          <div className="loader">
            <span classNAme="loader__text">Loading...</span>
          </div>
        ) : (
          <div className="movies">
            {movies.map((movie) => {
              return (
                <Movie
                  key={movie.id}
                  year={movie.year}
                  title={movie.title}
                  summary={movie.summary}
                  poster={movie.medium_cover_image}
                  genres={movie.genres}
                />
              );
            })}
          </div>
        )}
      </section>
    );
  }
}

export default App;

그리고 Movie 컴포넌트에 장르가 출력될 수 있도록 코드를 수정한다

<ul className="movie__genres">
            {genres.map((genre)=>{
                return <li className="movie__genre">{genre}</li>;

            })}
        </ul>
import React from 'react';
import PropTypes from 'prop-types';


function Movie({ title, year, summary, poster, genres }) {
  return (
    <div className="movie">
      <img src={poster} alt={title} title={title} />
      <div className="movie__data">
        <h3 className="movie__title">{title}</h3>
        <h5 className="movie__year">{year}</h5>
        <ul className="movie__genres">
            {genres.map((genre)=>{
                return <li className="movie__genre">{genre}</li>;

            })}
        </ul>
        <p className="movie__summary">{summary}</p>
      </div>
    </div>
  );
}

Movie.propTypes = {
  year: PropTypes.number.isRequired,
  title: PropTypes.string.isRequired,
  summary: PropTypes.string.isRequired,
  poster: PropTypes.string.isRequired,
  genres:PropTypes.arrayOf(PropTypes.string).isRequired,
};

export default Movie;

 

위와 같은 코드를 짜면 오류가 발생된다.

앞에서 잠깐 얘기했지만 컴포넌트를 여러개 출력할 때는 유일한 값을 이용하여 key props를 추가해야한다.

그런데 API에서 movies는 id라는 식별가능한 유일한 키가 있는데 genres에는 그런게 없다.

이런 경우에는 어떻게 해야할까?

map함수의 두번째 인자로 index를 주면된다.

자세한 사항은 map함수에 대해 찾아보기 바란다.

 

<ul className="movie__genres">
            {genres.map((genre,index)=>{
                return <li key={index}className="movie__genre">{genre}</li>;

            })}
        </ul>
import React from 'react';
import PropTypes from 'prop-types';


function Movie({ title, year, summary, poster, genres }) {
  return (
    <div className="movie">
      <img src={poster} alt={title} title={title} />
      <div className="movie__data">
        <h3 className="movie__title">{title}</h3>
        <h5 className="movie__year">{year}</h5>
        <ul className="movie__genres">
            {genres.map((genre,index)=>{
                return <li key={index} className="movie__genre">{genre}</li>;

            })}
        </ul>
        <p className="movie__summary">{summary}</p>
      </div>
    </div>
  );
}

Movie.propTypes = {
  year: PropTypes.number.isRequired,
  title: PropTypes.string.isRequired,
  summary: PropTypes.string.isRequired,
  poster: PropTypes.string.isRequired,
  genres:PropTypes.arrayOf(PropTypes.string).isRequired,
};

export default Movie;

2. CSS로 스타일링하기

우선 Movies.css와 App.css를 만든다.

그 후 Movie.js와 App.js에 import 시킨다.

 

그 다음 아래 코드를 그냥 복사한다.

css수업이 아니므로 자세한 사항은 생략한다.

 

App.css

* {
  box-sizing: border-box;
}

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%;
}

 

Movie.css

.movies .movie {
    background-color: white;
    margin-bottom: 70px;
    font-weight: 300;
    padding: 20px;
    border-radius: 5px;
    color: #adaeb9;
    box-shadow: 0 13px 27px -5px rgba(50, 50, 93, 0.25), 0 8px 16px -8px rgba(0, 0, 0, 0.3),
      0 -6px 16px -6px rgba(0, 0, 0, 0.025);
  }
  
  .movies .movie a {
    display: grid;
    grid-template-columns: minmax(150px, 1fr) 2fr;
    grid-gap: 20px;
    text-decoration: none;
    color: inherit;
  }
  
  .movie img {
    position: relative;
    top: -50px;
    max-width: 150px;
    width: 100%;
    margin-right: 30px;
    box-shadow: 0 30px 60px -12px rgba(50, 50, 93, 0.25), 0 18px 36px -18px rgba(0, 0, 0, 0.3),
      0 -12px 36px -8px rgba(0, 0, 0, 0.025);
  }
  
  .movie .movie__title,
  .movie .movie__year {
    margin: 0;
    font-weight: 300;
  }
  
  .movie .movie__title {
    margin-bottom: 5px;
    font-size: 24px;
    color: #2c2c2c;
  }
  
  .movie .movie__genres {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-wrap: wrap;
    margin: 5px 0px;
  }
  
  .movie__genres li,
  .movie .movie__year {
    margin-right: 10px;
    font-size: 14px;
  }

 

영화 요약을 180자로 제한하기 위해 아래 코드를 수정한다.

<p className="movie__summary">{summary.slice(0,180)}</p>
import React from 'react';
import PropTypes from 'prop-types';
import './Movie.css';

function Movie({ title, year, summary, poster, genres }) {
  return (
    <div className="movie">
      <img src={poster} alt={title} title={title} />
      <div className="movie__data">
        <h3 className="movie__title">{title}</h3>
        <h5 className="movie__year">{year}</h5>
        <ul className="movie__genres">
            {genres.map((genre,index)=>{
                return <li key={index} className="movie__genre">{genre}</li>;

            })}
        </ul>
        <p className="movie__summary">{summary.slice(0,180)}</p>
      </div>
    </div>
  );
}

Movie.propTypes = {
  year: PropTypes.number.isRequired,
  title: PropTypes.string.isRequired,
  summary: PropTypes.string.isRequired,
  poster: PropTypes.string.isRequired,
  genres:PropTypes.arrayOf(PropTypes.string).isRequired,
};

export default Movie;

 

뜨는 제목을 바꾸기 위해 index에서 Movie App으로 바꿔준다.


3. 영화앱 여러 기능 추가하기-react-router-dom 설치하고 프로젝트 폴더 정리하기

◆네비게이션 기능

Home과 About을 만들어서 각가을 클릭하면 해당 화면으로 이동시키는 메뉴이다.

이렇게 화면을 이동시키기 위해서는 장치가 필요한데 그것을 라우터라고 하며

라우터는 react-router-dom 패키지를 이용하면 쉽게 도입 가능하다.

 

cmd나 터미널에 명령어 입력

npm install react-router-dom

 

 

우선 src폴더 아래에 components폴더랑 routes폴더를 만든다.

그 다음 components에 Movie.css, Movie.js 파일 이동

routes폴더에는 About.js, Home.js를 만들어준다.

그런다음 App.js 코드를 Home.js에 복사하고 이름변경에 맞게 수정해준다.

 

Home.js

import React from 'react';
import axios from 'axios';
import Movie from '../components/Movie';
import './Home.css';

class Home extends React.Component {
  state = {
    isLoading: true,
    movies: [],
  };
  getMovies = async () => {
    const {
      data: {
        data: { movies },
      },
    } = await axios.get('https://yts-proxy.now.sh/list_movies.json?sort_by=rating');
    this.setState({ movies, isLoading: false });
  };
  componentDidMount() {
    this.getMovies();
  }
  render() {
    const { isLoading, movies } = this.state;
    return (
      <section className="container">
        {isLoading ? (
          <div className="loader">
            <span className="loader__text">Loading...</span>
          </div>
        ) : (
          <div className="movies">
            {movies.map((movie) => {
              return (
                <Movie
                  key={movie.id}
                  year={movie.year}
                  title={movie.title}
                  summary={movie.summary}
                  poster={movie.medium_cover_image}
                  genres={movie.genres}
                />
              );
            })}
          </div>
        )}
      </section>
    );
  }
}

export default Home;

 

Home.css

.container {
  height: 100%;
  display: flex;
  justify-content: center;
}

.loader {
  width: 100%;
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  font-weight: 300;
}
.movies {
  display: grid;
  grid-template-columns: repeat(2, minmax(400px, 1fr));
  grid-gap: 100px;
  padding: 50px;
  width: 80%;
  padding-top: 70px;
}

@media screen and (max-width: 1090px) {
  .movies {
    grid-template-columns: 1fr;
    width: 100%;
  }
}

 

App.js도 수정

import React from 'react';
import Home from './routes/Home';
import './App.css';


function App(){
  return <Home/>;
}

export default App;

4.  영화앱 여러 기능 추가하기-라우터 만들기

라우터는 사용자가 입력한 URL을 통해 특정 컴포넌트를 불러준다.

예를 들어 사용자가 localhost:3000/about이라고 입력하면 About 컴포넌트를 불러주는 것이다.

react-router-dom은 여러 종류의 라우터를 제공하는데 우리는 HashRouter과 Route 컴포넌트를 사용할 것이다.

 

App.js 코드

 

<Route path="/about" element={<About/>}/>

이렇게 작성해주면 localhost:3000/#/about 주소일 경우

About 컴포넌트 화면을 보여준다.

home도 마찬가지

 

 

import React from 'react';
import './App.css';
import {HashRouter,Route, Routes} from 'react-router-dom';
import About from './routes/About';
import Home from './routes/Home';


function App(){
  return(
    <HashRouter>
      <Routes>
      <Route path="/" element={<Home/>}/>
      <Route path="/about" element={<About/>}/>
      </Routes>
    </HashRouter>
  )
}

export default App;

 

About컴포넌트에도 내용이 있어야하므로 간단하게 만들어준다.

 

About.js

import React from 'react';
import './About.css';

function About() {
  return (
    <div className="about__container">
      <span>
        “Freedom is the freedom to say that two plus two make four. If that is granted, all else
        follows.”
      </span>
      <span>- George Orwell, 1984</span>
    </div>
  );
}

export default About;

About.css

.about__container {
  box-shadow: 0 13px 27px -5px rgba(50, 50, 93, 0.25), 0 8px 16px -8px rgba(0, 0, 0, 0.3),
    0 -6px 16px -6px rgba(0, 0, 0, 0.025);
  padding: 20px;
  border-radius: 5px;
  background-color: white;
  margin: 0 auto;
  margin-top: 100px;
  width: 100%;
  max-width: 400px;
  font-weight: 300;
}

.about__container span:first-child {
  font-size: 20px;
}
.about__container span:last-child {
  display: block;
  margin-top: 10px;
}


5. 영화앱 여러 기능 추가하기-내비게이션 만들어보기

라우터가 준비되었으므로 내비게이션을 통해 다른 화면으로 이동하면 된다.

 

먼저 components폴더에 Navigation.js를 만든다.

그리고 Navigation.css 파일도 만든다.

App 컴포넌트에 Navigation 컴포넌트를 import 시킨다

 

페이지 이동시키는 거니까 html a태그 쓰면 되겠지라고 생각하겠지만

a태그는 링크를 누를 때마다 리액트가 죽고 새 페이지가 열리는 문제점이 있어서

react-router-dom의 Link 컴포넌트를 사용한다.

 

Navigation.js

import React from 'react';
import { Link } from 'react-router-dom';
import './Navigation.css';

function Navigation() {
  return (
    <div className="nav">
      <Link to="/">Home</Link>
      <Link to="/about">About</Link>
    </div>
  );
}

export default Navigation;

 

App.js

import React from 'react';
import './App.css';
import {HashRouter,Route, Routes} from 'react-router-dom';
import About from './routes/About';
import Home from './routes/Home';
import Navigation from './components/Navigation';

function App(){
  return(
    <HashRouter>
      <Navigation/>
      <Routes>
      <Route path="/" element={<Home/>}/>
      <Route path="/about" element={<About/>}/>
      </Routes>
    </HashRouter>
  )
}

export default App;

 

Navigation.css

.nav {
    z-index: 1;
    position: fixed;
    top: 50px;
    left: 10px;
    display: flex;
    flex-direction: column;
    background-color: white;
    padding: 10px 20px;
    box-shadow: 0 13px 27px -5px rgba(50, 50, 93, 0.25), 0 8px 16px -8px rgba(0, 0, 0, 0.3),
      0 -6px 16px -6px rgba(0, 0, 0, 0.025);
    border-radius: 5px;
  }
  
  @media screen and (max-width: 1090px) {
    .nav {
      left: initial;
      top: initial;
      bottom: 0px;
      width: 100%;
    }
  }
  
  .nav a {
    text-decoration: none;
    color: #0008fc;
    text-transform: uppercase;
    font-size: 12px;
    text-align: center;
    font-weight: 600;
  }
  
  .nav a:not(:last-child) {
    margin-bottom: 20px;
  }

 


6. 영화앱 여러 기능 추가하기-영화 상세 정보 기능 만들어보기

Home에서 볼 수 있는 영화 정보는 아주 일부분인데 영화 카드를 누르면 상세 정보를 보여주는 기능을 만들 것이다.

 

이 기능을 만들기 위해서는 route props를 알아야한다

route props는 라우팅 대상이 되는 커포넌트에 넘겨주는 기본 props를 말하는데

우리가 직접 넘겨주지 않아도 기본으로 넘어가는 route props라는 것이 있고 이것을 이용해야 영화 데이터를 상세 정보 컴포넌트에 전달할 수 있다.

 

우선 Link를 import 하고

카드를 누르면 상세 정보를 보여줘야하므로 영화 div 태그안에 Link태그로 감싼다.

그리고 Link에 클릭시 state값을 넘겨준다.

import React from "react";
import PropTypes from "prop-types";
import "./Movie.css";
import { Link } from "react-router-dom";  //여기 

function Movie({ title, year, summary, poster, genres }) {
  return (
    <div className="movie">
      <Link //여기
        to={"/movie-detail"}
        state={{ year, title, summary, poster, genres }}
      >
        <img src={poster} alt={title} title={title} />
        <div className="movie__data">
          <h3 className="movie__title">{title}</h3>
          <h5 className="movie__year">{year}</h5>
          <ul className="movie__genres">
            {genres.map((genre, index) => {
              return (
                <li key={index} className="movie__genre">
                  {genre}
                </li>
              );
            })}
          </ul>
          <p className="movie__summary">{summary.slice(0, 180)}</p>
        </div>
      </Link> //여기
    </div>
  );
}

Movie.propTypes = {
  year: PropTypes.number.isRequired,
  title: PropTypes.string.isRequired,
  summary: PropTypes.string.isRequired,
  poster: PropTypes.string.isRequired,
  genres: PropTypes.arrayOf(PropTypes.string).isRequired,
};

export default Movie;

 

카드를 누르면 detail화면에 넘어가게 만들었으니 이제 detail 컴포넌트를 만들자

 

detail 컴포넌트를 우선 간단하게 만들어주고

import React from 'react';
import {useLocation} from "react-router-dom";

function Detail(props){
    const location=useLocation();
    console.log(location);
    return <span>hello</span>;
    }

export default Detail;

 

App.js에 Detail import 추가하고

route path 추가해준다.

import React from 'react';
import './App.css';
import {HashRouter,Route, Routes} from 'react-router-dom';
import About from './routes/About';
import Home from './routes/Home';
import Navigation from './components/Navigation';
import Detail from './routes/Detail'; //여기

function App(){
  return(
    <HashRouter>
      <Navigation/>
      <Routes>
      <Route path="/"  element={<Home/>}/>
      <Route path="/about" element={<About/>}/>
      <Route path="/movie-detail" element={<Detail/>}/> //여기
      </Routes>
    </HashRouter>
  )
}

export default App;

state 값이 잘 들어가져있는 것을 볼 수 있다


7. 영화앱 여러 기능 추가하기-리다이렉트 기능 만들어 보기

영화 카드를 누르면 state가 잘 들어있는데 URL로 직접 /movie-detail 쳐서 들어가면 state값이 null은 것을 확인할 수 있다

Detail 컴포넌트로 영화 데이터가 넘어가지 못했기 때문에 이런 경우 사용자를 강제로 Home으로 돌려보내야한다.

이런 기능을 리다이렉트 기능이라고 부른다.

 

Detail.js 파일을 아래와 같이 바꾼다

import React from 'react';
import {useLocation, useNavigate} from "react-router-dom";
import { useEffect } from 'react';

function Detail(props){
    const location=useLocation();
    const navigate = useNavigate();
    useEffect(() => {
        
        if(location.state===null){
            navigate('/'); //home으로 되돌아 가는 기능
        }
});
   
    if(location.state){
        return <span>{location.state.title}</span> //제목 출력
    }
    return null;
    }

export default Detail;

 

영화앱을 완성했다. 이제 배포해보자.


8. 영화 앱 깃허브에 배포하기

아래 링크보고 따라하면 된다

https://velog.io/@nemo/github-page-deploy-%EB%B0%B0%ED%8F%AC

 


참고문헌

Do it! 클론 코딩 영화 평점 웹서비스-니꼴라스

[React] Link를 통해 State 전달하기 ( Router v6 ) (velog.io)

https://blog.woolta.com/categories/1/posts/211

https://velog.io/@boyfromthewell/React-useLocation-%EC%82%AC%EC%9A%A9%EC%8B%9C-state-%EA%B0%92%EC%9D%B4-null%EB%A1%9C-%EB%93%A4%EC%96%B4%EC%98%A4%EB%8A%94-%EB%AC%B8%EC%A0%9C

 

*이 게시글은 Do it! 클론 코딩 영화 평점 웹서비스를 보고 정리한 글입니다.


목차

1. 로딩 구현

2. 영화 API 가져오기

3. 영화 데이터 화면에 그리기

4. 화면 스타일링 하기


1. 로딩 구현

import React from "react";

class App extends React.Component {
  state = {
    isLoading: true,
  };

  render() {
    const { isLoading } = this.state;  //구조분해할당
    return <div>{isLoading ? "Loading..." : "We are ready"}</div>; //삼항연산자
  }
}

export default App;

구조분해할당을 모르면 이해 자체가 안될 것이다

구조 분해 할당 - JavaScript | MDN (mozilla.org)

 

위 설명을 잘 참고하길 바란다.

 

state 내부의 isLoading을 개별 변수로 사용하기 위해 구조분해할당 기법을 사용하였다. 

구조분해할당한 덕분에 isLoading을 변수로 쓸 수 있게 되었다.

 

삼항연산자는

isLoading이 True면 Loading이 출력되고 False면 We are ready가 출력된다.

 

영화를 담아야하니 state에 movies라는 배열도 추가한다

import React from "react";

class App extends React.Component {
  state = {
    isLoading: true,
    movies: [],
  };

  render() {
    const { isLoading } = this.state;
    return <div>{isLoading ? "Loading..." : "We are ready"}</div>;
  }
}

export default App;

2. 영화 API 가져오기

우선 render()함수가 실행되면 호출되는 생명주기 함수가 바로 componentDidMount()함수이다.

이 함수를 이용해서 렌더링된 후 바로 화면에 나와야할 영화데이터를 넣을 것이다.

 

원래는 자바스크립트의 fetch() 함수를 알아야하지만 초보자가 사용하기는 난이도가 있어서 Axios라는 도구를 사용한다.

npm install axios

API Documentation - YTS YIFY

영화 데이터를 위 사이트에서 가져올 것이다

 

아래와 같이 App.js 파일을 수정한다.

화면에는 Loading...만 나오지만 오류가 나오지 않으므로 axios는 잘 작동하는 것이다.

import React from "react";
import axios from 'axios';

class App extends React.Component {
  state = {
    isLoading: true,
    movies: [],
  };
  componentDidMount(){
    axios.get('https://yts-proxy.now.sh/list_movies.json');
  }

  render() {
    const { isLoading } = this.state;
    return <div>{isLoading ? "Loading..." : "We are ready"}</div>;
  }
}

export default App;

브라우저에 F12를 눌러서 나오는 관리자모드에 네트워크에 들어가면

list_movies.json이라는 것이 보이는데 이게 axios가 API를 호출하고 있기 때문에 생긴 것이다

 

axios는 네트워크를 사용하기 때문에 느리게 작동한다.

그래서 axios.get()이 반환한 영화 데이터를 잡으려면 자바스크립트에서 axios.get()을 포함하고 있는 함수의 실행이 끝날 때 까지 시간이 걸릴 수 있다고 알려줘야한다.

 

getMovies라는 함수를 만들고 그 함수 안에서 axios.get()이 실행되도록 만든다.

그리고 axios.get()이 반환한 결과를 movies에 저장한다.

import React from "react";
import axios from 'axios';

class App extends React.Component {
  state = {
    isLoading: true,
    movies: [],
  };

  getMovies=()=>{
    const movies =axios.get('https://yts-proxy.now.sh/list_movies.json');
  }

  componentDidMount(){
    this.getMovies();
  }

  render() {
    const { isLoading } = this.state;
    return <div>{isLoading ? "Loading..." : "We are ready"}</div>;
  }
}

export default App;

 

이렇게 하면 componentDidMount()함수가 실행되면 this.getMovies()가 실행될 것이다.

이때 자바스크립트에게 getMovies()함수는 시간이 좀 필요하다고 말해야만

axios.get()이 반환한 데이터를 제대로 잡을 수 있다.

그것을 하기 위해서 async, await라는 키워드가 필요하다.

 

자바스크립트에게 getMovies()함수는 시간이 필요하다고 알리기위해 async를 쓰고

axios.get()의 실행을 기다려 달라고 말해주기 위해 await가 필요하다

import React from "react";
import axios from 'axios';

class App extends React.Component {
  state = {
    isLoading: true,
    movies: [],
  };

  getMovies=async ()=>{
    const movies =await axios.get('https://yts-proxy.now.sh/list_movies.json');
  }

  componentDidMount(){
    this.getMovies();
  }

  render() {
    const { isLoading } = this.state;
    return <div>{isLoading ? "Loading..." : "We are ready"}</div>;
  }
}

export default App;

 

중간정리

리액트 앱이 실행되면 최초로 render()함수가 실행되고 최초의 state에는 isLoading, movies가 있다.

isLoading은 true이고 movies는 빈배열이다

 

이어서 App 컴포넌트가 마운트되면 componentDidMount() 함수가 실행되면서 getMovies() 함수가 실행된다.

여기서 getMovies()함수에 시간이 많이 걸리는 axios.get()이 포함돼있어 async와 awawit를 붙여준다.


3. 영화 데이터 화면에 그리기

 

console.log(movies)라고 해보자.

import React from "react";
import axios from 'axios';

class App extends React.Component {
  state = {
    isLoading: true,
    movies: [],
  };

  getMovies=async ()=>{
    const movies =await axios.get('https://yts-proxy.now.sh/list_movies.json');
    console.log(movies);
    
  }

  componentDidMount(){
    this.getMovies();
    
  }

  render() {
    const { isLoading } = this.state;
    return <div>{isLoading ? "Loading..." : "We are ready"}</div>;
  }
}

export default App;

콘솔에 보면 우리가 영화 사이트를 만들기 위해 필요한 정보들이 들어있다.

data->data->movies 순서대로 개갸체에 접근하면 원하는 데이터를 추출할 수 있을 것이다

 

console.log(movies.data.data.movies)라고 해보자.

import React from "react";
import axios from 'axios';

class App extends React.Component {
  state = {
    isLoading: true,
    movies: [],
  };

  getMovies=async ()=>{
    const movies =await axios.get('https://yts-proxy.now.sh/list_movies.json');
    console.log(movies.data.data.movies);
    
  }

  componentDidMount(){
    this.getMovies();
    
  }

  render() {
    const { isLoading } = this.state;
    return <div>{isLoading ? "Loading..." : "We are ready"}</div>;
  }
}

export default App;

 

원하는대로 원하는 영화 데이터만 추출할 수 있게 된 것이다

movies.data.data.movies로 객체에 접근하는 것은 아름답지않아서 아래와같이 구조분해할당으로 코드를 수정한다.

import React from "react";
import axios from 'axios';

class App extends React.Component {
  state = {
    isLoading: true,
    movies: [],
  };

  getMovies=async ()=>{
    const {data:{
      data:{
        movies
      }
    }} =await axios.get('https://yts-proxy.now.sh/list_movies.json');
    console.log(movies);
    
  }

  componentDidMount(){
    this.getMovies();
    
  }

  render() {
    const { isLoading } = this.state;
    return <div>{isLoading ? "Loading..." : "We are ready"}</div>;
  }
}

export default App;

똑같은 결과인 것을 볼 수 있다.

 

데이터 추출을 했으니 이제 이 영화 데이터를 state에 저장하면 될 것이다.

console.log를 지우고 this.setState({movies:movies});를 입력한다.

 

setState는 State 값을 변경해준다.

빈 배열이던 movies state에 movies(영화 데이터)를 넣어준다.

import React from "react";
import axios from 'axios';

class App extends React.Component {
  state = {
    isLoading: true,
    movies: [],
  };

  getMovies=async ()=>{
    const {data:{
      data:{
        movies
      }
    }} =await axios.get('https://yts-proxy.now.sh/list_movies.json');
    this.setState({movies:movies});
  }

  componentDidMount(){
    this.getMovies();
  }

  render() {
    const { isLoading } = this.state;
    return <div>{isLoading ? "Loading..." : "We are ready"}</div>;
  }
}

export default App;

그런데 자바스크립트 ES6에서는 객체의 키와 대입할 변수의 이름이 같다면 코드를 축약할 수 있다.

this.setState({movies:movies});를

this.setState({movies})로 쓸 수 있는 것이다

import React from "react";
import axios from 'axios';

class App extends React.Component {
  state = {
    isLoading: true,
    movies: [],
  };

  getMovies=async ()=>{
    const {data:{
      data:{
        movies
      }
    }} =await axios.get('https://yts-proxy.now.sh/list_movies.json');
    this.setState({movies});
  }

  componentDidMount(){
    this.getMovies();
    
  }

  render() {
    const { isLoading } = this.state;
    return <div>{isLoading ? "Loading..." : "We are ready"}</div>;
  }
}

export default App;

 

영화데이터를 받았으니 Loading화면이 아니라 We are ready가 출력돼야할 것이다.

따라서 setState에 isLoading을 false로 바꾸는 내용을 추가한다.

 this.setState({movies, isLoading: false});

import React from "react";
import axios from 'axios';

class App extends React.Component {
  state = {
    isLoading: true,
    movies: [],
  };

  getMovies=async ()=>{
    const {data:{
      data:{
        movies
      }
    }} =await axios.get('https://yts-proxy.now.sh/list_movies.json');
    this.setState({movies, isLoading: false});
    
  }



  componentDidMount(){
    this.getMovies();
    
  }

  render() {
    const { isLoading } = this.state;
    return <div>{isLoading ? "Loading..." : "We are ready"}</div>;
  }
}

export default App;

 

영화데이터를 받았고 영화데이터를 받은 후 isLoading, Movies state값을 변경해줬지만 화면에는 영화 데이터가 아닌 We are ready만 뜬다.

우리는 We are ready가 아니라 영화 정보가 렌더링돼야 하므로 movies state를 그리기 위해 Movies 컴포넌트를 만든다.

 

Movie.js라는 파일을 만든다.

Movie 컴포넌트는 state가 필요하지 않으므로 함수형 컴포넌트로 만든다.

 

아래와 같이 뼈대를 만든다.

import React from 'react';
import PropTypes from 'prop-types';

function Movie(){
 return <h1></h1>;
}


Movie.propTypes = {};

export default Movie;

 

위 링크에서 필요한 데이터가 뭔지 일단 확인한다.

아래와 같이 안뜨면 크롬브라우저 스토어에서 JSON 뷰어를 설치하기 바란다.

필요한 데이터를 확인하고

개발자가 실수하는 것을 방지하기 위해 우선 propTypes를 작성한다.

주의할 것은 poster에는 image의 주소가 들어간다

API에서는 medium_cover_image라고 했지만 이해하기 쉽도록 poster라고 지정했다.

import React from 'react';
import PropTypes from 'prop-types';


function Movie() {
  return (
    <div>
    
    </div>
  );
}

Movie.propTypes = {
  id : PropTypes.number.isRequired,
  year: PropTypes.number.isRequired,
  title: PropTypes.string.isRequired,
  summary: PropTypes.string.isRequired,
  poster: PropTypes.string.isRequired,
};

export default Movie;

그냥 영화를 보여주면 재미없으므로 랭킹 순서대로 보여주기 위해 axios.get()에 주소를 수정한다.

await axios.get('https://yts-proxy.now.sh/list_movies.json?sort_by=rating');
import React from "react";
import axios from 'axios';

class App extends React.Component {
  state = {
    isLoading: true,
    movies: [],
  };

  getMovies=async ()=>{
    const {data:{
      data:{
        movies
      }
    }} =await axios.get('https://yts-proxy.now.sh/list_movies.json?sort_by=rating');
    this.setState({movies, isLoading: false});
  }

  componentDidMount(){
    this.getMovies();
  }

  render() {
    const { isLoading } = this.state;
    return <div>{isLoading ? "Loading..." : "We are ready"}</div>;
  }
}

export default App;

 

이제 평점 내림차순으로 영화 데이터를 가져올 수 있게 되었으므로 App 컴포넌트에서 Movie 컴포넌트로 id, title, year, summary ,poster props를 넘겨주면 된다. 

 

아래와 같이 구조 분해 할당으로 props를 받는다.

import React from 'react';
import PropTypes from 'prop-types';


function Movie({ id,title, year, summary, poster }) {
  return (
    <div >
      <img src={poster} alt={title} title={title} />
      <div >
        <h3 >{title}</h3>
        <h5 >{year}</h5>
        <p >{summary}</p>
      </div>
    </div>
  );
}

Movie.propTypes = {
  id : PropTypes.number.isRequired,
  year: PropTypes.number.isRequired,
  title: PropTypes.string.isRequired,
  summary: PropTypes.string.isRequired,
  poster: PropTypes.string.isRequired,
};

export default Movie;

 

그 후 App.js(App 컴포넌트)로 가서 영화 데이터에 props를 전달할 수 있도록 수정한다.

 

제일 먼저 Movie component import(가져오기)를 한다.

import Movie from'./Movie';

 

그다음 We are ready를 지우고 movies.map()으로 변경한다.

movie라는 변수에 movies 배열의 요소 하나하나를 다 저장하고 콘솔화면에 그 변수를 출력하는 코드로 수정한다.

그 후 Movie 컴포넌트를 리턴한다.

Movie 컴포넌트를 리턴하긴 했지만 props를 전달하지 않아 아무것도 출력되지 않는다.

return <div>{isLoading ? "Loading..." : movies.map(
      (movie)=>{
        console.log(movie);
        return <Movie />;
    }
import React from "react";
import axios from 'axios';
import Movie from'./Movie';

class App extends React.Component {
  state = {
    isLoading: true,
    movies: [],
  };

  getMovies=async ()=>{
    const {data:{
      data:{
        movies
      }
    }} =await axios.get('https://yts-proxy.now.sh/list_movies.json?sort_by=rating');
    this.setState({movies, isLoading: false});
  }

  componentDidMount(){
    this.getMovies();
  }

  render() {
    const { isLoading, movies } = this.state;
    return <div>{isLoading ? "Loading..." : movies.map(
      (movie)=>{
        console.log(movie);
        return <Movie />;
    }
    )}</div>;
  }
}

export default App;

 

Movie 컴포넌트에 props를 전달한다.

   return <Movie 
        id={movie.id}
        year={movie.year}
        title={movie.title}
        summary={movie.summary}
        poster={movie.medium_cover_image}

        
        />;
import React from "react";
import axios from 'axios';
import Movie from'./Movie';

class App extends React.Component {
  state = {
    isLoading: true,
    movies: [],
  };

  getMovies=async ()=>{
    const {data:{
      data:{
        movies
      }
    }} =await axios.get('https://yts-proxy.now.sh/list_movies.json?sort_by=rating');
    this.setState({movies, isLoading: false});
  }

  componentDidMount(){
    this.getMovies();
  }

  render() {
    const { isLoading, movies } = this.state;
    return <div>{isLoading ? "Loading..." : movies.map(
      (movie)=>{
        console.log(movie);
        return <Movie 
        id={movie.id}
        year={movie.year}
        title={movie.title}
        summary={movie.summary}
        poster={movie.medium_cover_image}

        
        />;
    }
    )}</div>;
  }
}

export default App;

 아래와 같이 화면에 잘 뜨는 것을 볼 수 있다.

 

마지막으로

 

App 컴포넌트에 

console.log(movie)는 필요없으니 지우고 key 값을 추가한다.

 

아래와 같이 컴포넌트를 여러개 출력할 때는 유일한 값을 이용하여 key props를 추가해야한다.

   return <div>{isLoading ? "Loading..." : movies.map(
      (movie)=>{
        return <Movie 
        key={movie.id} //이 부분
        id={movie.id}
        year={movie.year}
        title={movie.title}
        summary={movie.summary}
        poster={movie.medium_cover_image}
        />;
    }
    )}</div>;

 

import React from "react";
import axios from 'axios';
import Movie from'./Movie';

class App extends React.Component {
  state = {
    isLoading: true,
    movies: [],
  };

  getMovies=async ()=>{
    const {data:{
      data:{
        movies
      }
    }} =await axios.get('https://yts-proxy.now.sh/list_movies.json?sort_by=rating');
    this.setState({movies, isLoading: false});
  }

  componentDidMount(){
    this.getMovies();
  }

  render() {
    const { isLoading, movies } = this.state;
    return <div>{isLoading ? "Loading..." : movies.map(
      (movie)=>{
        return <Movie 
        key={movie.id}
        id={movie.id}
        year={movie.year}
        title={movie.title}
        summary={movie.summary}
        poster={movie.medium_cover_image}
        />;
    }
    )}</div>;
  }
}

export default App;

출처

Do it! 클론 코딩 영화 평점 웹서비스-니꼴라스

 

*이 게시글은 Do it! 클론 코딩 영화 평점 웹서비스를 보고 정리한 글입니다.


목차

1. create-react-app으로 리액트 폴더 생성

2. 파일 수정

3. git허브에 리액트 앱 업로드 하기

4. src 폴더 정리하기, index.js, App.js 수정


Movie App (nomadcoders.github.io)

우리가 만들고자 하는 사이트는 위와 같은 영화 평점 웹서비스 사이트이다.


1.  create-react-app으로 리액트 폴더 생성

원하는 위치에 이동후 아래와 같은 명령어를 cmd나 shell, 터미널에 입력

보통 폴더를 하나 만들고 그 위치에 만든다.

npx create-react-app movie_app_2020

다 완료되면 아래와 같은 메시지와 함께 폴더에 여러 파일들이 들어가진다.

아래와 같은 명령어를 또 친다.

 cd movie_app_2020
 npm start

그러면 아래와 같은 화면이 뜰 것이다.

준비단계 끝

터미널 창을 끄면 리액트 앱 연결도 끊기니 터미널은 계속 켜놔야한다.

 

다음으로 해당 폴더를 비주얼코드로 들어간다.

아래와 같이 많은 파일이 보인다.


2. 파일 수정

README.md 파일 다 지우고 아래와 같이 입력

package.json파일에 test, eject 삭제


3. git허브에 리액트 앱 업로드 하기

git없으면 git 설치 먼저 하기

Git - Downloads (git-scm.com)

 

1) 명령어 입력

cd movie_app_2020 
git init

 

2) 깃허브에 저장소 만들기

New repository · GitHub

회원 가입 후 위 사이트 접속

 

아래와 같이 입력후 create repository입력

.git으로 끝나는 주소가 저장소 주소이다.

.ㅎ

 

3) 아래와 같이 명령어 입력

git add .
git commit -m "02-2깃허브에 리액트 앱 업로드하기"
git branch -M main
git remote add origin https://github.com/windbrain/movie_app_2020.git
git push -u origin main

 

4) 잘 생성된 것을 볼 수 있다.


4. src 폴더 정리하기, App.js 수정

App.css

App.test.js

index.css

logo.svg

reportWebBitals.js

setupTests.js

package-lock.json

삭제

index.js 정리

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';


const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
    <App />
);

 

App.js 정리

function App() {
  return <div className="App"/>;
}
export default App;

 

정리하고 화면 확인

아무것도 안나오는 것이 정상


출처

Do it! 클론 코딩 영화 평점 웹서비스-니꼴라스

*이 게시글은 인프런-처음 만난 리액트 강좌를 보고 정리한 글입니다.


목차

1. Component

2. Props

3. Props의 특징

4. Props의 사용법

5. Component 만들기

6. Component 렌더링

7. Component 합성

8. Component 추출


1. Component

리액트는 Component 기반구조라는 것을 배웠었다.

리액트는 모든 페이지가 컴포넌트로 구성돼있고 그 컴포넌트는 또다른 여러개 컴포넌트의 조합으로 이뤄져있다.

이런 식으로 레고 블록을 조립하듯 컴포넌트들을 모아서 개발할 수 있는 것이다

 

아래가 바로 컴포넌트로 페이지를 만든 하나의 예시이다.

출처 : 처음 만난 리액트-인프런 강좌

 

리액트의 컴포넌트는 개념적으로는 자바스크립트의 함수와 비슷하다.

출처 : 처음 만난 리액트-인프런 강좌

기본 메커니즘은 자바스크립트의 함수와 동일하지만 입, 출력이 조금 다르다.

 

리액트 컴포넌트에서 입력은 바로 다음에 배울 Props라는 것이고

출력은 앞에서 배운 React Element가 된다.

 

즉 리액트는 어떠한 속성을 입력받아서 그에 맞는 element를 생성하여 리턴해주는 것이다.

출처 : 처음 만난 리액트-인프런 강좌

이는 마치 붕어빵틀을 통해 붕어빵을 만드는데 슈크림, 팥, 고구마등의 앙코(팥소)를 넣어서 각각의 붕어빵을 만드는 것과 비슷하다.

슈크림, 팥, 고구마 등의 앙코가 props(속성)

붕어빵틀이 Component

만들어진 붕어빵이 element인 것이다.

출처 : 처음 만난 리액트-인프런 강좌


2. Props

props는 prop이 여러개인 것을 의미하는데 prop는 Property를 줄여서 쓴 것으로 

속성이라는 의미이다.

바로 컴포넌트의 속성이다.

아래 붕어빵 그림을 보면 쉽게 이해할 수 있다.

앙코가 props인 것이다.

출처 : 처음 만난 리액트-인프런 강좌

 

실제 예시로 Props를 알아보자.

출처 : 처음 만난 리액트-인프런 강좌

위 컴포넌트를 A컴포넌트라고 하면 다 같은 컴포넌트에서 생성된 녀석들이지만 각기 다른 이미지와 텍스트를 가지고 있는 것은 아래와 같이 Props가 다르기 때문이다.

출처 : 처음 만난 리액트-인프런 강좌

 

Props를 다시 정리하면 다음과 같다.

컴포넌트에 전달할 다양한 정보를 담고 있는 자바스크립트 객체인 것이다. 

 

우리가 컴포넌트에 데이터를 전달하고 전달된 데이터에 따라 다른 모습의 엘리먼트를 렌더링하고 싶을 때 해당 데이터를 부모 컴포넌트에서  props에 넣어서 전달하는 것이다.

출처 : 처음 만난 리액트-인프런 강좌


3. Props의 특징

1) 읽기전용(Read-Only)

읽기 전용이라는 것은 값을 변경할 수 없다는 것이다.

출처 : 처음 만난 리액트-인프런 강좌

따라서 새로운 Props를 Component에 전달해줘야한다. 

출처 : 처음 만난 리액트-인프런 강좌

 

제대로 이해하기 위해서는 자바스크립트 함수의 속성에 대해 알아야한다

출처 : 처음 만난 리액트-인프런 강좌

위의 함수는 입력값이 바로 return이 되므로 입력값이 바뀌지 않는다. 이런 것을 Pure(순수)하다고 한다.

 

아래의 함수는 입력값이 바뀌게 되므로 Impure(순수x)하다고 한다.

출처 : 처음 만난 리액트-인프런 강좌

React의 공식 문서에는 다음과 같이 설명하는데

모든 리액트 컴포넌트는 Props를 직접 바꿀 수 없고

같은 Props에 대해서는 항상 같은 결과를 보여줘야한다는 의미이다.

 

출처 : 처음 만난 리액트-인프런 강좌


4. Props의 특징

아래의 코드 같은 경우는 JSX코드인데

아래 그림과 같이 

키=값 형태로 props를 넣을 수 있다.

 

App 컴포넌트가 나오고

그 안에 Profile 컴포넌트가 나오는데

그 컴포넌트의 Props의 값을 넣어주는 형태이다.

 

Props의 문자열 외의 값을 넣을 때(정수, 변수, 다른 컴포넌트)는 중괄호를 통해 감싸줘야한다.

출처 : 처음 만난 리액트-인프런 강좌

props를 전달하면 아래와 같은 자바스크립트 객체가 된다.

출처 : 처음 만난 리액트-인프런 강좌

 

그리고 다음과 같이 Props에 중괄호를 사용해 다른 컴포넌트도 넣을 수 있다. 

출처 : 처음 만난 리액트-인프런 강좌

 

JSX코드를 사용하지 않은 코드는 다음과 같다.

출처 : 처음 만난 리액트-인프런 강좌
출처 : 처음 만난 리액트-인프런 강좌



5. Component 만들기

리액트 컴포넌트는 아래 그림과 같이 Function Component(함수 컴포넌트)와 Class Component(클래스 컴포넌트)로 나뉜다. 

리액트의 초기 버전에서는 Class Component를 많이 사용하였으나 쓰기 불편하다는 이야기가 많아 Function Component를 더 많이 사용하게 되었다.

함수 컴포넌트를 개선하는 과정에서 만들어진 것이 Hook이라는 개념이다. 

출처 : 처음 만난 리액트-인프런 강좌

 

1) 함수 컴포넌트

아래와 같은 형태가 함수 컴포넌트로 Welcome 컴포넌트 안에 저렇게 props에 name을 '바보'라고 키=값 형태로 props를 넣으면 {props.name}자리에 바보라고 출력되는 것을 볼 수 있다.

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(

    <Welcome name="바보"/>
 
);

출처 : 처음 만난 리액트-인프런 강좌

화살표함수로 나타내면 아래와 같이 쓸 수 있다.

const Welcome=(props)=>{
    return <h1>안녕, {props.name}</h1>;
}

2) 클래스 컴포넌트

클래스 컴포넌트는 함수 컴포넌트에 비해서 몇가지 추가적인 기능을 가진다.

 

아래의 클래스 컴포넌트 코드는 위의 함수 컴포넌트와 동일한 기능을 수행한다.

모든 클래스 컴포넌트는 Component를 상속받아서 만들어진다.

 

아래와 같이 Component를 import시켜야 사용가능하다.

import {Component} from 'react';

 

*상속은 객체지향에서 나오는 개념으로 한 클래스에 변수들과 함수들을 상속받아서 새로 자식 클래스를 만드는 방법이다.

 

★컴포넌트를 만들 때 주의점

Component 이름은 항상 대문자로 시작해야한다

React는 소문자로 시작하는 component를 DOM 태그로 인식하기 때문이다.

출처 : 처음 만난 리액트-인프런 강좌


6. Component 렌더링

아래 코드는

Welcome 함수 컴포넌트를 선언하고

element를 생성한 후 생성된 element를 root div태그에 렌더링을 한다.

따라서 name="인제"라는 props값이 props.name이 전달된다. 

출처 : 처음 만난 리액트-인프런 강좌


7. Component 합성

Component안에는 또 다른 Component를 쓸 수 있기 때문에 복잡한 화면을 여러 개의 Component로 나눠서 구현 가능하다.

 

아래는 Welcome 컴포넌트의 props를 다르게 해서 Welcome 컴포넌트를 여러번 사용하는 것을 볼 수 있다.

이렇게 여러개의 Component를 합쳐서 또 다른 Component를 만드는 것을 Component 합성이라고 한다.

출처 : 처음 만난 리액트-인프런 강좌

 

아래와 같은 형태일때 App Component가 Welcome Component의 부모 컴포넌트인데

부모 컴포넌트에서 아래와 같이 props값을 설정할 수 있다.

출처 : 처음 만난 리액트-인프런 강좌


8. Component 추출

component합성과는 반대로 복잡한 Component를 여러개의 Component로 쪼개는 것을 Component 추출이라고 한다.

Component 추출을 잘 활용하게 되면 재사용성이 올라가는데  컴포넌트가 작아질 수록 컴포넌트의 기능과 목적이 명확해지고 props도 단순해지기 때문에 다른 곳에서 사용할 수 있을 확률이 높아지기 때문이다.

재사용성이 올라가기 때문에 개발속도도 빨라진다.

 

아래 코드의 정확한 분석이 아닌 흐름만 봐보도록 하자.

아래와 같은 코드가 있다.

Component 추출을 해보자.

 

아래와 같이 Avator 컴포넌트로 추출을 했다.

아래와 같이 전보다 깔끔해진 것을 볼 수 있다.

 

마찬가지로 아래코드를 추출했다.

 

아래와 같이 코드가 직관적이고 가독성도 높게 바뀌었다.

최종적으로 아래와 같은 형태가 되었다.


참고문헌

1. [무료] 처음 만난 리액트(React) - 인프런 | 강의 (inflearn.com)

*이 게시글은 인프런-처음 만난 리액트 강좌를 보고 정리한 글입니다.


목차

1. Elements란?

2. createElement가 동작하는 과정

3. React elements의 특징

4. Elements 렌더링하기


1. Elements란?

Elements의 영단어는 어떤 물체를 구성하는 요소, 성분이라는 뜻인데

마찬가지로 여기서 배우는 Elements도 리액트 앱을 구성하는 요소를 뜻한다.

 

아래는 공식사이트에서 elements를 설명하고 있는 문장인데 Elements는 리액트 앱에서 가장 작은 블록들이라고 적혀있다.

출처 : 처음 만난 리액트-인프런 강좌

 

이렇게 Chrome 브라우저에서 개발자도구의 Elements에서 볼 수 있는 것은 React Elements가 아니라 Dom Elements이다.

출처 : 처음 만난 리액트-인프런 강좌

 

리액트를 개발하기 시작한 아주 초장기에 화면에 나타나는 내용을 기술하는 자바스크립트 객체를 일컫는 용어가 필요했는데 처음에는 Descriptor이라고 불리다가 최종적으로 나타나는 형태는 DOM Elements여서

통일성을 위해 똑같이 Elements라 부르기로 결정했다.

출처 : 처음 만난 리액트-인프런 강좌

즉, 아래와 같이 createElement(현재는 다른 방식으로 사용됨)의 결과로 객체가 생성되는데 그게 element이다.

출처 : 처음 만난 리액트-인프런 강좌

React elements는 앞에서 배웠던 Virtual DOM의 Elements를 나타낸다.

출처 : 처음 만난 리액트-인프런 강좌

Elements는 화면에서 보이는 것들을 기술하는데 아래와 같은 JSX코드를 작성하면 내부적으로는 JS코드로 변환하여

React Elements가 생성된다 

Element는 HTML에서 태그로 적은 노드들을 지칭하므로 가상DOM에 <h1> element가 만들어지는 것이다.

출처 : 처음 만난 리액트-인프런 강좌

 

이 React Elements는 앞에서도 말했지만 자바스크립트 객체 형태로 존재하며 

마음대로 변경할 수 없는 불변성을 가지고 있다

 

출처 : 처음 만난 리액트-인프런 강좌

아래는 button을 나타내기위한 Element이다

(JSX코드가 JS코드로 바뀌면서 생성되는 자바스크립트 객체인 element)

출처 : 처음 만난 리액트-인프런 강좌

위 코드를 렌더링하면 아래와 같은 DOM element가 된다

출처 : 처음 만난 리액트-인프런 강좌


2. createElement의 동작과정

다시 복습을 하면 아래와 같은 JSX코드를 내부적으로는 바벨이 JS코드로 변환시키는데 그 변환시킨 JS코드가 createElement이고 그 createElement를 사용하면 자바스크립트 객체인 React Element가 만들어진다.

출처 : 처음 만난 리액트-인프런 강좌

아래는 React.createElement의 구조인데

type에는 문자열로된 html태그나 또다른 React Component가 들어간다.

모든 React Component는 최종적으로는 html태그를 사용하게된다.(하나의 컴포넌트는 여러개의 자식 컴포넌트로 구성되며 그 자식 컴포넌트를 분해하면 결국 html태그가 남게 된다)

 

props는 일단 간단하게 속성이라고 생각하면 되는데 태그에 class나 style같은 속성을 attribute라고 하는데 이 attribute보다 상위에 있는 복잡한 개념이고 일단 대충 속성이라는 것이라고 알고 넘어가자.

 

children은 해당 element의 자식 element가 여기 들어가게 된다.

출처 : 처음 만난 리액트-인프런 강좌

 

아래에는 Button 컴포넌트와 ConfirmDialog 컴포넌트가 있는데

ConfirmDialog 컴포넌트가 Button 컴포넌트를 포함하고 있다

출처 : 처음 만난 리액트-인프런 강좌

이런 ConfirmDiag 컴포넌트의 element의 형태는 어떻게 될까?

바로 다음과 같이 된다. 

 

1번째 children은 p태그이고

2번째 children은 리액트 component인 Button component이며

confirmdiag컴포넌트 element는 button컴포넌트의 element를 만들어서 합치게 된다

출처 : 처음 만난 리액트-인프런 강좌

그래서 최종적으로 confirmDiag 컴포넌트의 element의 형태는 다음과 같다.

출처 : 처음 만난 리액트-인프런 강좌


3. React elements의 특징

1) 불변성(immutable)

Element가 변하지 않는다면 화면이 어떻게 업데이트되는 것인가 궁금할 것이다.

출처 : 처음 만난 리액트-인프런 강좌

 

아래는 붕어빵을 예로 든 것이다.

붕어빵 틀로 붕어빵을 만드는데 안에 있는 붕어빵 앙코(팥소)를 바꿀 수 없는 것과 같은 이치이다.

앙코를 바꿀 순 없으니 새로 붕어빵을 만들어서 바꿔치기 하면 된다.

 

출처 : 처음 만난 리액트-인프런 강좌

Virtual DOM의 개념을 다시한번 상기시키면 된다.

바뀐 Element를 업데이트한다.

출처 : 처음 만난 리액트-인프런 강좌

리액트로 개발을 하다보면 상태관리와 더불어 화면이 얼마나 자주바뀌는지가 성능에 큰 영향을 미친다


4. Elements 렌더링하기

element를 만든 이후 화면에 보여주기 위해서는 렌더링이라는 과정을 거쳐야 한다

 

먼저 아래 코드를 보자.

아래 코드는 id가 root인 div태그이다.

아주 간단한 코드이지만 모든 리액트 앱에 들어가는 아주 중요한 코드이다.

 

실제로 이 div 태그안에 react element가 렌더링되며 이것을 root DOM node라고 한다.

div태그에 들어있는 모든 것이 react element에 의해 관리된다. 

 

출처 : 처음 만난 리액트-인프런 강좌

오직 리액트만으로 만들어진 모든 웹사이트들은 단 하나의 root DOM node를 가지며

기존에 있는 웹사이트에 리액트를 추가적으로 연동하게 되면 여러개의 분리된 수많은 root DOM node를 가질 수 있다

 

아래와 같이 최상단에 있는 node가 Root DOM Node이다

출처 : 처음 만난 리액트-인프런 강좌
출처 : 처음 만난 리액트-인프런 강좌

위 같은 div태그에react element를 렌더링하기 위해서는 다음과 같은 코드를 사용한다

 

element를 먼저 생성하고 생성된 element를 root div태그에 렌더링하는 코드이다.

이때 사용되는  render라는 함수는 render(a , b )의 형태에서

react elements인 a를 

html elements(Dom elements)인 b에 렌더링하는 역할을 한다.

 

오늘의 핵심은 다음과 같다.

element는 한번 생성되면 바뀔 수 없으므로 업데이트를 하기 위해서는 다시 생성해야한다.


참고문헌

1. [무료] 처음 만난 리액트(React) - 인프런 | 강의 (inflearn.com)

*이 게시글은 인프런-처음 만난 리액트 강좌를 보고 정리한 글입니다.


목차

1. JSX란?

2. JSX의 역할

3. JSX의 장점

4. JSX 사용법

5. React에서 Style 적용하는법

6. 주석 다는법


1. JSX란? 

JSX는 자바스크립트 확장 문법이라는 의미로 자바스크립트의 문법을 확장시킨 것이다.

출처 : 처음 만난 리액트-인프런 강좌

JSX는 JavaScript와 XML/HTML을 합친 것이라고 보면 된다.

아래는 간단한 JSX코드이다.

출처 : 처음 만난 리액트-인프런 강좌

왼쪽은 자바스크립트 문법이고 오른쪽은 html처럼 생겼는데 이게 뭔 식이지 생각했을텐데..

이 코드가 자바스크립트코드와 html코드가 결합돼있는 JSX코드인 것이다.


2. JSX의 역할

JSX코드는 내부적으로 XML/HTML 코드를 자바스크립트로 변환하는 과정을 거치게 된다.

출처 : 처음 만난 리액트-인프런 강좌

 

아래는 Hello라는 이름의 React Component가 나오고 Component 내부에 JSX코드가 사용된 것을 볼 수 있다. 

그리고 이렇게 만든 컴포넌트를 ReactDom의 redner 함수를 이용해 실제 화면에 렌더링을 하고 있다. 

출처 : 처음 만난 리액트-인프런 강좌

위와 아래 코드는 완전 동일한 동작을 하는 코드이다.

JSX코드도 내부적으로는 createElement를 사용하도록 변환되기 때문이다.

 

이렇게 JSX코드를 내부적으로는 createElement로 변환해주는 녀석이 Babel이라는 녀석이다.

Babel이라는 JavaScript Compiler가 해주는 역할이며

Babel의 수많은 플로그인 중 JSX를 JS로 변환해주는 것을 일반적으로 JSX Transform이라고 부른다.

출처 : 처음 만난 리액트-인프런 강좌

 

출처 : 처음 만난 리액트-인프런 강좌

 

createElement의 결과로는 아래와 같은 객체가 생성된다.

객체는 자바스크립트에서 key와 value의 쌍으로 이루어진 집합을 의미한다.

React는 객체를 읽어서 DOM을 만들어서 사용하고 항상 최신 상태를 유지한다.

React에는 이 객체를 element라고 한다.

 

type은 element의 유형을 나타내고(div태그나 span태그 같은 html태그가 올 수 도 있고 다른 react 컴포넌트가 올 수 도 있음)

props는 속성이 들어가고

children는 이 element가 포함하고 있는 자식 element라고 보면 된다.

출처 : 처음 만난 리액트-인프런 강좌

이렇게 JSX를 사용하기 위해서는 React.createElement로 의존해야했었는데 성능을 향상시키지 못하는 문제점이 있었다.

그래서 현재 리액트에서는 아래와 같은 형태로 JSX Transform이 별도로 분리되었고

Babel에서는 이것을 이용하여 JSX를 JS로 변환하게 된다.

function App() {
  return <h1>Hello World</h1>;
}

위 같은 JSX코드를 내부적으로는 아래와 같은 JS로 변환해주는 것이다.

import {jsx as _jsx} from 'react/jsx-runtime';

function App() {
  return _jsx('h1', { children: 'Hello world' });
}

 

 

리액트에서 JSX를 쓰는 것이 필수는 아니지만 JSX를 사용하면 장점들이 많기 때문에 편리해진다.(생산성, 가독성)


3. JSX의 장점

1) 간결한 코드, 가독성 향상, 버그발견하기 쉬움

출처 : 처음 만난 리액트-인프런 강좌

2) Injection Attacks 방어해서 보안 향상

Injection Attack은 쉽게 말해서 입력창에 문자나 숫자같은 값이 아닌 소스코드를 입력하여 해당 소스코드가 실행되게 만드는 수법으로 예를 들어 아이디를 넣는 입력창에 자바스크립트 코드를 넣었는데 그 코드가 그대로 실행돼버리면 문제가 생길 수 있을 것이다. 

 

기본적으로 ReactDOM은 렌더링하기 전에 임베딩된 값을 모두 문자열로 변환한다.

그래서 명시적으로 선언되지 않은 값은 괄호사이에 들어갈 수 없다

이것이 결과적으로 XSS를 막을 수 있다. 

 

*임베딩 :  사람이 쓰는 자연어를 기계가 이해할 수 있는 숫자의 나열인 벡터로 바꾼 결과 혹은 그 과정 전체를 의미한다.

출처 : 처음 만난 리액트-인프런 강좌


4. JSX 사용법

컴포넌트에 여러 요소가 있다면 반드시 부모 요소 하나로 감싸야 한다.

리액트 컴포넌트에서 요소 여러 개를 하나의 요소로 감싸야 하는 이유는 Virtual DOM에서 컴포넌트 변화를 감지해 낼 때 효율적으로 비교할 수 있도록 컴포넌트 내부는 하나의 DOM 트리 구조로 이루어져야 한다는 규칙이 있기 때문이다.

 

아래와 같이 최종적으로는 하나의 요소로 감싸줘야한다.

 

기본적으로 자바스크립트를 확장한 것이므로 모든 자바스크립트 문법을 지원한다.

 

XML/HTML 코드를 사용하다가 중간에 JavaScript 코드를 사용해야할 때는 {} 중괄호를 사용한다.

출처 : 처음 만난 리액트-인프런 강좌

태그의 속성에 값을 넣고싶을 때는 큰따옴표 사이에 문자열을 넣거나 중괄호 사이에 자바스크립트 코드를 넣으면 된다.

출처 : 처음 만난 리액트-인프런 강좌


5. React에서 Style 적용하는법

 

html에서 style을 바로 적용하는 방법을 인라인 스타일 시트라고 하는데 

<p style="color: blue">Lorem ipsum dolor.</p>

이런 인라인 스타일링을 리액트에서는 다음과 같이 표현한다.

 

리액트에서 DOM 요소에 스타일을 적용할 때는 문자열 형태로 넣는 것이 아니라 객체 형태로 넣어줘야하는 것이다.

자바스크립트 코드니까 중괄호가 있는데 객체형태이므로 또 중괄호가 생겨 이중 중괄호 형태가 된 것이다.

 

또한 스타일 이름 중에서 background-color처럼 -문자가 포함되는 이름은 -문자를 없애고 카멜 표기법으로 작성한다.

background-color는 backgroundColor로 작성한다.

<p style={{color: blue}}>Lorem ipsum dolor.</p>

 

일반 HTML에서 CSS 클래스를 사용할때는 class 속성을 사용하지만 JSX에서는 class가 아닌 className으로 설정해줘야한다.

HTML에서는 <input>태그 처럼 닫아주지않아도 작동하지만 JSX에서는 <input/>태그 처럼 무조건 닫아줘야한다.


6. 주석 다는법

JSX 내부에서 주석을 작성할때는 {/*   */} 형식으로 작성한다.


참고문헌

1. [무료] 처음 만난 리액트(React) - 인프런 | 강의 (inflearn.com)

2. babel-plugin-transform-react-jsx - npm (npmjs.com)

3. Introducing the New JSX Transform – React Blog (reactjs.org)

4. 리액트를 다루는 기술-김민준

 

*이 게시글은 인프런-처음 만난 리액트 강좌를 보고 정리한 글입니다.


목차

1. 직접 리액트 연동하는 법

2. create-react-app

3. create-react-app하면 생성되는 파일 구성

4. react 파일의 기본 구조


1. 직접 리액트 연동하는 법

 

아래와 같이 index.html 파일을 보면 <div id="root"></div>를 볼 수 있는데

DOM Container(Root DOM Node)으로 

 

일단은 Virtual DOM의 시작점이라고 알고 넘어가자.

 

script태그를 이용해 리액트와 리액트 컴포넌트를 가져온다.

<html> <!-- index.html -->
    <head>
        <title>소플의 블로그</title>
        <link rel="stylesheet" href="styles.css">
    </head>
    <body>
        <h1>소플의 블로그에 오신 여러분을 환영합니다!</h1>

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

        <!-- 리액트 가져오기 -->
        <script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
        <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js " crossorign></script>

        <!-- 리액트 컴포넌트 가져오기 -->
        <script src="MyButton.js"></script>
    </body>
</html>

css는 리액트 구동과는 관련없으므로 안해줘도 상관없다.

h1{  /*styles.css*/
    color:green;
    font-style:italic;

}

 

My button.js 파일인데 무슨 말인지 모르겠지만 우선 그냥 타이핑하고 넘어가자.

//My button.js

function MyButton(prpos){ 
    const [isClicked, setIsClicked] = React.useState(false);

    return React.createElement(
        'button',
        {onClick: () => setIsClicked(true)},
        isClicked ? 'Clicked!': 'Click here!'
    )
}

const domContainer=document.querySelector('#root');
ReactDOM.render(React.createElement(MyButton),domContainer);

위의 파일을 실행시키면 아래와 같은 화면이 나오며

Click here! 버튼을 클릭하면 Clicked!로 바뀌는 것을 볼 수 있다

이것은 리액트 컴포넌트의 state가 변경됐기 때문이다

지금까지 한 과정은 HTML로 아주 간단한 웹사이트를 만든 뒤 웹사이트에 <script></script>를 이용해 리액트를 추가한 뒤 실제로 리액트 Component까지 만들어서 렌더링을 해보았다.

리액트로 웹사이트를 만들 때마다 이러한 환경설정을 해야한다면 굉장히 번거롭기 때문에 

곧바로 리액트 프로젝트를 만들 수 있도록 리액트를 자동으로 생성해주는 create-react-app 패키지를 이용한다


2. create-react-app

리액트로 웹 애플리케이션을 개발하는데 필요한 모든 설정이 들어있는 상태로 프로젝트를 생성해주는 도구이다.

(이전에는 웹팩, 바벨과 같은 도구가 필요했다)

이것을 사용하기 위해서는 우선 아래와 같이 Node.js, npm, VS Code가 필요하다

 

출처 : 인프런-처음 만난 리액트 강좌

create-react-app은 npx명령어를 사용하면 실행할 수 있다.

명령어는 VS COde의 터미널이나 Powershell, cmd 아무데서나 해도 괜찮다.

출처 : 인프런-처음 만난 리액트 강좌

 

npx create-react-app my-app을 하면 아래와 같이 뜨면서 만들어진다.

my-app자리에는 이름 아무거나 해도 상관없다.

 

실행이 다 됐다면

cd my-app  (엔터)

npm start (엔터)

를 해주면 된다.

출처 : 인프런-처음 만난 리액트 강좌

cd는 change directory의 약자로 경로 변경을 해준다. 

cd명령어를 이용해 create-react-app으로 만든 my-app폴더안으로 경로를 이동시킨후 npm start(애플리케이션)을 실행시킨다.

아래와 같이 웹 브라우저가 뜨는 것을 알 수 있다.

그 후 VS Code IDE에 들어가면 해당경로에 아래와 같이 파일이 생성된 것을 볼 수 있다.


3. create-react-app하면 생성되는 파일 구성

1) READ.md

프로젝트의 내용을 설명하기 위해 사용하는 파일로써 프로젝트가 어떤 목적에 의해 개발되었는지, 어떻게 사용할 수 있는지 등을 적어 놓는 파일

이 파일은 깃허브 업로드 이후 확인 가능

 

2) package.json

프로젝트에 대한 설명, 종속성 패키지, 실행 스크립트 등의 정보를 담는 매니페이스(Manifest) 파일

 

3) favicon.ico

브라우저 제목과 함께 표시되는 아이콘


4. react 파일의 기본 구조

▶import 구문(불러오기)

import는 특정 파일을 불러오는 것을 의미하는데 리액트로 만든 프로젝트의 자바스크립트 파일에서는 import파일을 사용하여 다른 파일들을 불러와 사용할 수 있다.

 

*이렇게 모듈을 불러와서 사용하는 것은 사실 원래 브라우저에 없던 기능인데 이렇게 브라우저가 아닌 환경에서 자바스크립트를 실행할 수 있게 해주는 환경인 Node.js에서 지원하는 기능이다. 

이러한 기능을 브라우저에서도 사용하기 위해 번들러(bundler)를 사용하는데 번들은 묶는다는 뜻으로 파일을 묶듯이 연결한다는 것이다.

 

리액트 프로젝트에서는 번들러를 주로 웹팩으로 사용하는데 번들러 도구를 사용하면 import로 모듈을 불러왔을 때 불러온 모듈을 모두 합쳐서 하나의 파일을 생성해준다.

 

index.js를 시작으로 필요한 파일을 다 불러와서 번들링하게 된다.

 

웹팩에서 이렇게 파일들을 불러오는 것을 웹팩의 로더 기능이라고 하는데 이 덕분에 SVG, CSS, 웹 폰트, 미디어 파일 등을 불러올 수 있게 되는 것이다.

웹팩의 로더는 원래 직접 설치하고 설정해야 하지만 create-react-app이 번거로운 작업을 모두 대신 해주기에 별도의 설정이 필요 없다.

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

 

▶export 구문(내보내기)

export default App;

참고문헌

1. [무료] 처음 만난 리액트(React) - 인프런 | 강의 (inflearn.com)

2. Do it! 클론 코딩 영화 평점 웹서비스-니꼴라스

3. 리액트를 다루는 기술-김민준

 

+ Recent posts