본문 바로가기
개발/REACT

ReactJS 입문 with 니꼬쌤 2 - [JSX, STATE]

by 개발자 구리 2022. 3. 21.

지난 시간에 앞서서 이번 포스팅에서도 버튼의 클릭 수를 보여주는 간단한 웹을 구현해보면서

하나씩 살을 붙여 나가며, 진정한 React 코드까지 가보자..!

 

 

JSX

JSX(JavaScript eXtension)는 이름 그대로 JS를 확장한 문법이다.

React에서 HTML을 표현할때 사용하는 문법이며, 브라우저는 JSX를 모르기 때문에, 빌드 시 Babel이라는 code transformer에 의해 JS 코드로 변환된다. JSX를 사용하는 것의 이점은 JS 코드를 HTML처럼 표현할 수 있기 때문에 용이한 개발이 가능해진다는 것이다. 하지만 HTML에서 사용하는 예약어(class, for, onclick)들은 사용할 수 없기에 조금씩 차이가 있다.

 

아래 코드에서는 앞서 보았던 코드와 다르게 https://unpkg.com/@babel/standalone/babel.min.js가 추가된 것을 볼 수 있다.

<!DOCTYPE html>
<html>
  <body>
    <div id="root"></div>
  </body>
  <script src="https://unpkg.com/react@17.0.2/umd/react.production.min.js"></script>
  <script src="https://unpkg.com/react-dom@17.0.2/umd/react-dom.production.min.js"></script>
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
  <script type="text/babel">
    const root = document.getElementById("root");
    const Title = (
      <h3 id="title" onMouseEnter={() => console.log("mouse enter")}>
        Hello I'm a title
      </h3>
    );
    const Btn = (
      <button
        style={{ backgroundColor: "tomato" }}
        onClick={() => console.log("im clicked")}
      >
        Click me
      </button>
    );
    const container = React.createElement("div", null, [Title, Btn]);
    ReactDOM.render(container, root);
  </script>
</html>

>>

이전 코드에서 React.createElement로 쓰던 부분을 JSX를 사용해서 좀더 HTML스럽게 보이도록 했다.

JSX를 사용할때 컴포넌트의 첫 글자는 반드시 대문자여야 한다는 점을 기억하자.

소문자인 경우 React와 JSX는 이를 HTML 태그로 오인할 수 있기 때문.

 

JSX는 어플리케이션을 여러가지 작은 요소로 나누어 관리할 수 있도록 해준다.

위의 코드에서는 React.createElement("div", null, [Title, Btn])이라는 코드를 통해 Title과 Btn을 나타내는 작은 코드들을 합친 후에 렌더링하고 있다. 반면 아래의 코드에서는 Title과 Btn, Container 모두를 함수형식으로 구현했다. (두개 코드의 방식이 다르나 동작은 동일) 

<!DOCTYPE html>
<html>
  <body>
    <div id="root"></div>
  </body>
  <script src="https://unpkg.com/react@17.0.2/umd/react.production.min.js"></script>
  <script src="https://unpkg.com/react-dom@17.0.2/umd/react-dom.production.min.js"></script>
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
  <script type="text/babel">
    const root = document.getElementById("root");
    function Title() {
      return (
        <h3 id="title" onMouseEnter={() => console.log("mouse enter")}>
          Hello I'm a title
        </h3>
      );
    }
    const Btn = () => (
      <button
        style={{ backgroundColor: "tomato" }}
        onClick={() => console.log("im clicked")}
      >
        Click me
      </button>
    );
    const Container = () => (
      <div>
        <Title />
        <Btn />
      </div>
    );
    ReactDOM.render(<Container />, root);
  </script>
</html>

 

STATE

여기까지는 실제로 count가 올라가는 동작은 제외하고 문법과 형식에 대해 알아보았다.

이제부터는 다시 count가 올라가는 동작을 포함하는 코드를 보자. 중요하게 봐야할 것은, React가 렌더링을 수행하는 방식이다.

 

React JS가 아닌 일반 JS를 쓴 브라우저는 노드 정보가 바뀔 때마다 노드 트리를 처음부터 다시 생성한다.

하지만 React에서는 변경된 부분만 파악해서, 정말로 필요한 부분만 리렌더링한다.

 

가상DOM을 써서 우리 시야에 보이는 부분만 수정해서 보여주고 모든 업데이트가 끝나면 일괄로 합쳐서 실제 DOM에 던져주고, 이러한 과정에서 렌더트리 단계가 최적화된다고 한다. //면접때 많이 물어본다고 댓글에서 봤음! 공부해볼 예정//
그렇기 때문에 React를 이용해 웹앱이나 모바일 게임 등의 서비스를 만든다면 훨씬 가벼워지고 개발자가 구현하고 싶었던 그대로가 가넝하게 되는 것이 아닐까?

 

<!DOCTYPE html>
<html>
  <body>
    <div id="root"></div>
  </body>
  <script src="https://unpkg.com/react@17.0.2/umd/react.production.min.js"></script>
  <script src="https://unpkg.com/react-dom@17.0.2/umd/react-dom.production.min.js"></script>
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
  <script type="text/babel">
    const root = document.getElementById("root");
    let counter = 0;
    function countUp() {
      counter = counter + 1;
      render();
    }
    function render() {
      ReactDOM.render(<Container />, root);
    }
    function Title() {
      return (
        <h3 id="title" onMouseEnter={() => console.log("mouse enter")}>
          Total clicks: {counter}
        </h3>
      );
    }
    const Btn = () => (
      <button style={{ backgroundColor: "tomato" }} onClick={countUp}>
        Click me
      </button>
    );
    const Container = () => (
      <div>
        <Title />
        <Btn />
      </div>
    );
    render();
  </script>
</html>

위의 코드 중

let counter = 0;
function countUp() {
        counter = counter + 1;
}

는 counter라는 data를 주고, 그 data를 바꿀 수 있는 countUp이라는 function을 의미한다.

그리고 JSX 문법 중

const data = React.useState(0);

이 의미하는바 또한 동일하다. 이를 console에 찍어보면 [0, f]가 출력되는데,

다시말해 어떤 data[0]와, 그 data[0]를 바꿀 수 있는 function인 data[1]이라는 뜻이다.

 

data[0], data[1] 말고 더 아름답게 접근하는 방법은 없을까?

const x = [1, 2]
const a = x[0];
const b = x[1];

==

const x = [1, 2]
const [a, b] = x;

const [a, b] = x라는 표현 방식을 참고해서 아래와같은 코드를 작성할 수 있다.

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

<!DOCTYPE html>
<html>
  <body>
    <div id="root"></div>
  </body>
  <script src="https://unpkg.com/react@17.0.2/umd/react.production.min.js"></script>
  <script src="https://unpkg.com/react-dom@17.0.2/umd/react-dom.production.min.js"></script>
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
  <script type="text/babel">
    const root = document.getElementById("root");
    function 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);
  </script>
</html>

이렇게 state를 사용했을때의 장점에는 여러가지가 있다.

 

#1

이전에는 우리가 직접 렌더링을 했다. 값이 바뀌는 부분마다 ReactDOM.render를 호출했다.

하지만 React.useState를 이용하는 경우, 어떠한 값(state)이 변경되면 컴포넌트 자체가 새로운 값을 가지고 재생성되고, UI가 refresh 되며 리렌더링이 자동으로 발생한다. 직접 렌더링을 신경써서 해줄 필요가 없는 것이다.

 

#2

위의 코드에서는 setCounter(counter + 1);이라는 코드를 사용했지만

setCounter((current) => current + 1);을 쓰면 좀더 안전한 변경이 가능하다. 현재 state를 바탕으로 다음 state를 계산해내기 때문에, 예상치 못한 업데이트가 다른 곳에서 일어난다고 해도 그로 인한 혼동을 막을 수 있게 되는 것이다.

이전값을 이용해서 데이터를 업데이트하는 방법 외에도 setCounter(0) 등 직접 값을 넣어줄 수도 있다.

 

 

중간점검!!

처음 작성했던 HTML 코드와 현재까지 작성한 코드를 비교해보자.

  1. HTML 요소를 생성하거나 찾을 필요도 없고 (왜냐면 JS에서 바로 HTML을 만들었으니까)
  2. 이벤트 리스너를 더해줄 필요도 없고 (만든 HTML에 곧바로 이벤트 리스너를 더해주었으니까)
  3. UI를 업데이트해줄 필요도 없어졌다. (UI를 업데이트하면 자동으로 리렌더링되기 때문이니까)