프로그래밍/React

[React] ReactJS로 영화 웹 서비스 만들기

라다디 2021. 11. 13. 01:00

 

📌 아래 강의의 내용을 정리한 글입니다.

 


 

1.

리액트의 규칙 中 1

HTML을 직접 작성하지 않는다! 대신 자바스크립트 코드를 사용

span이든 button이든 무언가를 만들기 위해 js와 리액트를 이용해 element를 생성

 

리액트 JS의 기초를 배울 수 있는 어려운 방식(사용하지 않음)

body안에 어떻게 리액트 element를 가져다 두는가?

→ 리액트가 HTML을 생성! → 리액트 DOM 사용 필요

리액트는 어플리케이션을 인터렉티브하게 만들어주는 라이브러리

→ 엔진과 같음, 인터렉티브한 UI를 만들 수 있게 해줌

리액트 돔은 리액트 엘리먼트들을 HTML body에 둘 수 있게 해주는 라이브러리 혹은 패키지

→ 리액트 엘리먼트를 HTML에 두는 역할

 

리액트 JS는 바닐라 자바스크립트에서 사용하던 방식을 거꾸로 함

바닐라 자바스크립트에서는 HTML을 먼저 만들고 그걸 JS로 가져와서 HTML을 수정

리액트에서는 모든 것이 자바스크립트로 시작하고 HTML로 감

자바스크립트로 엘리먼트를 생성하고 이것을 리액트가 HTML로 번역

 

바닐라 자바스크립트에서는 이벤트 리스너를 적을 때 event만 적음

리액트에서는 이벤트 리스너를 적을 때 on + event

addEventListener를 반복하는 것 대신에 property에서 이벤트를 등록 가능

 

리액트는 property가 HTML에 놓여야한다는 것을 알고 있음

리액트는 또한 on + event가 이벤트 리스너인 것을 알고 있음

 

어려운 방식 정리

먼저 리액트JS와 리액트DOM을 import

→ 리액트 JS는 element를 생성하고 이벤트 리스너를 더하는 것을 도와줌

리액트 돔은 리액트 엘리먼트들을 가져다 HTML로 바꿔줌

 

1. body에 비어있는 div를 생성

→ div는 리액트 DOM이 리액트 element를 가져다 둘 곳

2. root div를 가져온 후에 ReactDom.render를 함

→ 리액트 엘리먼트들을 root div 안에서 보여주라는 뜻

 

ReactDOM.render(container, root);

: ReactDOM은 container element를 root div안에 render

const container = React.createElement("div", null, [span, btn])

: container는 div, 아무런 property도 없음, div의 콘텐츠

 

createElement

첫 번째 인수는 root에 들어갈 HTML태그

두 번째 인수는 props(properties)가 포함된 오브젝트

세 번째 인수는 콘텐츠

 

props는 일반적인 HTML 태그의 property + event listener

리액트가 훌륭한 이유!

property object에 event listener를 등록할 수 있다는 것!

addEventListener를 할 필요가 없음

 

⭐ props 안에서 event listener를 등록할 수 있다.

 

<!-- 바닐라 자바스크립트 -->
<!DOCTYPE html>
<body>
  <span>Total clicks: 0</span>
  <button id="btn">Click me</button>
</body>
<script>
  let counter = 0
  const button = document.getElementById("btn")
  const span = document.querySelector("span")
  function handleClick() {
    counter += 1
    span.innerText = `Total clicks: ${counter}`
  }
  button.addEventListener("click", handleClick)
</script>
</html>
  const root = document.getElementById("root");
  const span = React.createElement("span", null, "Hello I'm a span"); // 리액트로 element 생성
  // 두 번째 인수는 스팬의 프로퍼티들(id, class name 등)
  // 세 번째 인수는 스팬의 내용
  const btn = React.createElement("button", 
  {
    onClick: () => console.log("Im clicked!"),
    style: {
      backgroundColor: "tomato",
    }
  }, 
  "Click me");
  // property에 이벤트 리스너를 등록할 수 있음
  // 이런식으로 하나의 statement로 js에서 여러줄이던 코드를 작성할 수 있음
  const container = React.createElement("div", null, [span, btn]) // 2개의 component를 가지는 component를 생성한 것
  ReactDOM.render(container, root); // render의 의미 - 리액트 엘리먼트를 가지고 HTML로 만들어 배치한다는 것
  // 즉 사용자에게 보여준다
  // 스팬을 루트안에 렌더해달라고 말해줘야 함
  // span과 btn을 둘 다 렌더하고 싶다

 

2. 

JSX 소개 - React

createElement를 대체할 수 있는 방법

const btn = React.createElement("button", 
  {
    onClick: () => console.log("Im clicked!"),
    style: {
      backgroundColor: "tomato",
    }
  }, 
  "Click me");

이 부분을 대체하자! -> 조금 더 편리한 도구가 있음 (JSX)

 

JSX는 자바스크립트를 확장한 문법 기본적으로 리액트 요소를 만들 수 있게 해줌 HTML에서 사용한 문법과 흡사한 문법을 사용

const Title = 
	<h3 id="title" onMouseEnter={() => console.log("mouse enter")}>
		Hello I'm a h3
	</h3>

이게 바로 JSX! HTML과 유사하게 생김

태그를 열고 닫고, 내용은 그 태그 사이에 담기고, property는 마치 HTML 태그의 속성처럼 적으면 됨 이거 때문에 createElement 사용을 하지 않는 것

 

브라우저가 JSX를 이해할 수 있도록 뭔가 설치 필요 -> Babel 사용

Babel import 해주고 스크립트에 타입 적어줘야 함 type="text/babel"

 

Babel 사용시 JSX를 createElement를 사용한 방식으로 변경

Babel은 코드를 변환해주는 녀석(아래와 같이 바꿔줄 거임)

 

JSX로 적은 코드를 브라우저가 이해할 수 있는 형태로 바꿔주는 것 브라우저는 JSX를 모름

JSX를 invalid하다고 생각함 에러 메시지: Uncaught SyntaxError: Unexpected token '<'

→ Babel 사용해서 코드를 변환해줘야 함


    const root = document.getElementById("root");

    // JSX 사용
    const Title = (
      <h3 id="title" onMouseEnter={() => console.log("mouse enter")}>
        Hello I'm a h3
      </h3>
    );
    const Button = (
      <button
        onClick={() => console.log("Im clicked!")}
        style={{ backgroundColor: "tomato" }}
      >
        Click me
      </button>
    );

    const container = React.createElement("div", null, [Title, Button]);
    ReactDOM.render(container, root);
  

const container = React.createElement("div", null, [Title, Button]);

이번엔 이 코드를 다른 방식으로 적어주고 싶음

createElement 대신 JSX를 사용해서 표현하고 싶음

 

일단 대문자로 바꿔주고 <div>로 담아주고 내용으로 변수를 넣어보자 const Container = <div>Title Button</div>;

브라우저에서 보면 Title Button 이라는 텍스트가 뜸

이걸 원한게 아님!

 

Title이나 Button을 포함시키기 위해서 우리가 해야할 건 컴포넌트들을 함수화하는 것

const Title = (
      <h3 id="title" onMouseEnter={() => console.log("mouse enter")}>
        Hello I'm a h3
      </h3>
    );
const Title = () => (
      <h3 id="title" onMouseEnter={() => console.log("mouse enter")}>
        Hello I'm a h3
      </h3>
    );

화살표 함수로 함수화 해줌

 

화살표 함수를 사용하지 않는다면 아래와 같이

function Title() {
	return (
	<h3 id="title" onMouseEnter={() => console.log("mouse enter")}>
        		Hello I'm a h3
      	</h3>
	);
}

 

const Container = <div><Title/> <Button/></div>;

여러 컴포넌트들이 합쳐진 구성

div 태그를 렌더링하고 있는 컴포넌트가 하나 있는데, Title에 관련된 코드를 포함하고 있는 것

 

<Title/> 이런식으로 적어주는 건 아래와 동일

<div>
<h3 id="title" onMouseEnter={() => console.log("mouse enter")}>
        Hello I'm a h3
      </h3>
<Button/>
</div>

 

⭐ 컴포넌트의 첫 글자는 반드시 대문자여야 함

만약 소문자면 리액트와 JSX는 이게 HTML 태그라고 생각할 거임

 

직접 만든 요소는 전부 대문자로 시작해야함

만일 HTMl요소라면 이렇게 소문자로 적어주면 됨

const Container = (
      <div>
        <button>Hello</button> // 이건 HTML 태그이고
        <Button /> // 이건 내가 생성한 Button 
      </div>
    );

보다시피 JSX는 어플리케이션을 여러가지 작은 요소로 나누어 관리할 수 있게 해줌 여러 요소로 잘게 쪼개서 만들어서 합쳐주기만 하면 됨

한쪽에서 Title을 만들어주고 한쪽에선 Buttom을 만들어주고 이렇게 보기 쉽게 코드를 분리한 뒤에 함께 렌더링

 

바벨에 의해 변환된 코드를 보면 이럼

var Container = /*#**PURE***/React.createElement("div", null, /*#**PURE***/React.createElement(Title, null), " ", /*#**PURE***/React.createElement(Button, null));

 

⭐ 직접 만든 컴포넌트를 렌더링해서 다른 곳에서 사용할 때는 항상 대문자로 시작해야 함

const Container = (
      <div>
        <Title /> <Button />
      </div>
    );
    ReactDOM.render(Container, root);
const Container = () => (
      <div>
        <Title /> <Button />
      </div>
    );
    ReactDOM.render(<Container />, root);

이것도 이런식으로 바꿔주자

Container 함수화!

 

방금까지 배운 것

어떻게 하면 컴포넌트를 다른 컴포넌트 안에 넣는가?

 

    const root = document.getElementById("root");

    // JSX 사용
    const Title = () => (
      <h3 id="title" onMouseEnter={() => console.log("mouse enter")}>
        Hello I'm a h3
      </h3>
    );
    const Button = () => ( 
			// buttom React Element를 반환하고 있는 함수
      // 함수의 형태이기 때문에 코드를 재사용 편리
      <button
        onClick={() => console.log("Im clicked!")}
        style={{ backgroundColor: "tomato" }}
      >
        Click me
      </button>
    );

    const Container = () => (
      <div>
        <Title /> <Button />
      </div>
    );
    ReactDOM.render(<Container />, root);
  

 

3. 

state는 기본적으로 데이터가 저장되는 곳

vanilla.html의 counter를 state로 만들 수 있음 

바뀌는 데이터들을 state로

 

let counter = 0; 이런 변수들을 JSX에 전달하는 방법

바닐라 JS때는 span.innerText를 이용해서 span의 텍스트를 바꿔줌

리액트에서는 단지 중괄호를 열어주고 변수이름을 담아주면 끝 <h3>Total clicks: {counter}</h3>

 

그럼 값이 바뀔 때마다 화면에 보여주려면? 데이터를 바꾸고 렌더링!

데이터가 바뀔 때마다 Container를 리렌더링 해주는 것

우리가 리렌더링하면 이 컴포넌트도 바뀐 데이터를 가지고 재생성

 

리렌더링 방법1 (최선의 방법x)

    const root = document.getElementById("root");
    let counter = 10;
    function countUp() {
      counter = counter + 1;
      render();
    }
    function render() {
      ReactDOM.render(<Container />, root);
    }
    const Container = () => (
      // 리액트 엘리먼트 생성
      // 이 리액트 엘리먼트가 곧 div 태그이고 h3태그와 button태그를 담고 있음
      <div>
        <h3>Total clicks: {counter}</h3>
        <button onClick={countUp}>Click me</button>
      </div>
    );
    render();
  

최선의 방법이 아닌 이유?

값을 바꿀 때마다 다시 렌더링하는 걸 잊어서는 안됨

폼이나 로그인 페이지, 알림 등이 있고 이걸 계속 호출해야하는 경우 장난 아님

 

⭐ 매우 중요

크롬에서 개발자도구를 키고 Click me를 하면

바닐라에서는 body랑 span이 업데이트 되고 있는 것을 보여줌

 

리액트에서는 h3, button, div들은 업데이트 되지 않는게 보임 

리액트 JS는 UI에서 바뀐 부분만 업데이트 해주고 있음

리액트는 이전에 렌더링된 컴포넌트는 어떤 거였는지 확인

그리고 다음에 렌더링될 컴포넌트는 어떤지를 봄

리액트는 다른 부분만 파악

다시 Total clicks를 생성할 필요 없고 button도 다시 생성할 필요가 없고 오로지 바뀐 부분만 업데이트 해주면 됨

아주 좋은 것!

굉장히 인터렉티브한 것을 만들 수 있다는 뜻

여러가지 요소들을 리렌더링하려고 해도 전부 다 생성되는 게 아님!

오로지 바뀐 부분만 생성 유일하게 리렌더링 하는 건 오리지 counter의 숫자뿐

 

리액트를 사용하는 이유 인터렉티브한 걸 만들기에 최적화되어 있음

 

4.


    const root = document.getElementById("root");
    let counter = 10;
    function countUp() {
      counter = counter + 1;
      render();
    }
    function render() { // 최초 호출될 때 Container 컴포넌트를 root div에 담아줌
      ReactDOM.render(<Container />, root); // 최초 호출되면 Container 함수가 호출되고 Container함수가 호출되면 React Element가 반환
    }
    const Container = () => (
      <div>
        <h3>Total clicks: {counter}</h3> // 이 리액트 요소는 Total clicks와 counter 변수를 가짐
        <button onClick={countUp}>Click me</button> // JSX방법으로 이벤트 리스너 등록
      </div>
    );
    render();
  

지난 번에 배운 것

1. 컴포넌트나 JSX에 변수를 추가하고 싶을 땐 {}에 담는다

2. UI를 업데이트하고 싶으면(사용자에게 변화된 부분을 보여주고 싶다면) render함수를 다시 호출하면 됨

3. 버튼을 클릭할 때 Container 컴포넌트 전체를 리렌더링하지만 사실은 HTML코드에서는 오로지 숫자만 바뀜

render함수를 다시 호출하면 새 Container 컴포넌트를 전체 다 생성하는 게 x

하지만 리액트는 똑똑해서 새로 렌더링해도 전체를 전부 재생성할 필요없이 바뀐 부분만 새로 생성할 수 있게 도와줌

→ 문제: 데이터가 바뀔 때마다 render 함수를 호출하는 걸 잊어서는 안됨

 

축약: 초기 컴포넌트가 초기 데이터를 가지고 렌더링되고 버튼이 클릭되면 다시 렌더링

 

더 나은 리렌더링 방법이 없을까?

리액트 어플 내에서 데이터를 보관하고 자동으로 리렌더링을 일으킬 수 있는 최고의 방법을 배워보자

방법1처럼 함수를 계속 불러줄 필요 없이!

 

이번 강의에서 배울 것

리액트가 리렌더링을 어떻게 도와주는지 알아볼 것

 

리액트 어플리케이션을 다룰 때 어디에 데이터를 담으면 되는 걸까?

그 전에는 counter같은 변수에 담았었음

 

const data = React.useState();

[undefined, f]

이 코드에서 얻어지는 것은 undefined와 함수하나를 담은 배열 (배열 이름: data)

undefined가 data, 함수는 data를 바꿀 때 사용하는 함수

 

React.useState()함수는 초기값을 설정할 수도 있음

React.useState(0)

[0, f]

초기값인 0과 data의 값을 바꿀 수 있는 함수가 들어있는 배열을 얻음

 

let counter = 10;

function countUp() {

counter = counter + 1;

render();

} 의 역할을

const data = React.useState(0);

얘가 한 문장으로 해주고 있음

 

counter라는 data를 주고 그 counter 값을 바꿀 수 있는 함수를 줌

React.useState(0) 초기값을 0으로 주는 것은 let counter = 0과 같음

배열의 값을 접근하기 위해서는 data[0]과 같은 식으로 작성해줘야 함

보기 안좋음

 

자바스크립트의 문법

어떻게 하면 배열에서 요소들을 꺼내서 이름을 부여할 수 있을까?

 

const counter = data[0];

const modifier = data[1];

라고 변수에 담아줘도 되겠지만 더 좋은 방법이 있음

 

const food = ["tomato", "potato"]

const [myFavFood, mySecondFavFood] = food;

-> myFavFood의 값은 tomato

-> mySecondFavFood의 값은 potato

이렇게 하는 건 const myFavFood = food[0]과 똑같음

 

const [counter, modifier] = React.useState(0);

이런식으로 작성해주자!

자바스크립트 문법임

 

const x = [1, 2, 3];

const [a, b, c] = x;

 

다음에 배울 건 어떻게 modifier를 이용해서 counter의 값을 바꿔줄지 그리고 왜 modifier가 필요한지


    const root = document.getElementById("root");
    const App = () => {
      const [counter, modifier] = React.useState(0);
      console.log(data);
      return (
        <div>
          <h3>Total clicks: 0</h3>
          <button>Click me</button>
        </div>
      );
    };
    ReactDOM.render(<App />, root);
  

 

5. 

useState를 사용했을 때 useState는 배열을 하나 주는데

배열의 첫 번째 요소는 우리가 담으려는 data값

두번째 요소는 data값을 바꿀 때 사용할 modifier

 

왜 modifier가 필요한가?

 

modifier은 값을 하나 받음

어떤 값을 부여하든 modifier함수는 그 값으로 업데이트하고 리렌더링을 일으킴

modifier(343434)

아래와 동일

counter = 343434;

ReactDOM.render(<App />, root);

위 두 작업이 modifier 함수 안에서 일어남

 

이젠 단순히 한줄로 counter같은 데이터를 숫자형 데이터로 건네줄거고

그 데이터 값을 바꿀 함수도 함께 줄거야

그 함수를 이용해서 데이터를 바꿨을 때 데이터 값이 바뀌고 컴포넌트도 동시에 리렌더링 됨(업데이트)

 

보통 사람들은 데이터의 이름을 붙일 때 원하는대로 붙이고

함수의 이름은 set데이터이름과 같이 작성

따라서 반영해주면 setCounter

 

리액트는 업데이트 사이사이마다 정확히 어떤 것이 업데이트됐는지 파악해서 HTML에서 그 부분만 고치는 것


    const root = document.getElementById("root");
    const App = () => {
      const [counter, setCounter] = React.useState(0);
      const onClick = () => {
        setCounter(counter + 1);
      };
      return (
        // 이게 사용자가 보게 될 컴포넌트
        <div>
          <h3>Total clicks: {counter}</h3>
          <button onClick={onClick}>Click me</button>
        </div>
      );
    };
    ReactDOM.render(<App />, root);
  

 

6.

앱의 state를 바꾸는 법

setCounter(counter + 1);

좋은 방법이 아님

이전 단계의 state를 이용해서 현재 state를 바꾸려 했지만, 결과가 예상과 다르게 나올 수 있음

왜냐하면 counter는 다른 곳에서 업데이트될 수 있기 때문

 

state를 바꾸는 두가지 방법

1. setCounter를 이용해 원하는 값을 넣어주는 것(문자도 가능) : setCounter(5);

2. 이전 값을 이용해서 현재 값을 계산해내는 것 (위 코드가 이것, but 더 나은 방법이 있음)

-> 만약 현재 값을 가지고 계산을 해야한다면 ..

setCounter에는 함수를 넣을 수도 있음

이 함수의 첫 번째 인수는 현재 값

그리고 함수의 리턴 값이 새로운 state

setCounter(current => current + 1)

 

setCounter(counter + 1);

setCounter(current => current + 1);

두 코드 모두 현재의 state를 가지고 새로운 값을 계산해내고 있으나 아래쪽이 더 안전

왜냐하면 두 번째 방식처럼 함수를 사용했을 때 리액트가 이 current가 확실히 현재 값이라는 것을 보장

 

결론: 현재 state를 기반으로 계산을 하고 싶다면 함수를 이용하자!

 

setCounter(current => current + 1)

현재 state인 현재 counter값을 줌