C-log

⚛️React Training : useState의 간단한 원리 본문

⚛️React/⚡ver.2

⚛️React Training : useState의 간단한 원리

4:Bee 2024. 1. 26. 20:19
728x90

앞 시간에 우리는 React가 동작하는 원리를 React CDN을 사용해서 js로 풀어 나열 해보았다. js의 기본기가 있고 DOM에 대한 이해가 있다면 충분히 이해할 수 있는 포스팅이라고 본다. 이번 시간에는 useState의 동작원리를 살펴 볼 것이다.
useState의 동작원리를 살펴보기 전에 우리가 알아야할 개념이 있다. 바로 React의 업데이트 메커니즘이다. 업데이트 메커니즘은 Virtual DOM을 활용해서 효율 적으로 UI를 업데이트 하는 방식이다. React는 상태 업데이트를 한번에 모아서(최적화를 위해서) 작업을 수행한다. 즉, 각기 useState 상태가 업데이트가 될 때마다 순차적으로 업데이트가 되는 방식이 아니라 모든 준비부터 완료가 될때까지 한번에 처리 하는 것이다. 여기서 다음으로 알아야할 개념이 바로 비동기이다.

컴포넌트는 동기적으로 호출 되지만 일부는 비동기적이다. 하지만 비동기 작업을 수행한다는 기준으로 훑어 보면 React가 전체적으로 돌아가는 모습은 비동기적일 수 있다는 결론에 도달한다. 쉽게 말해서 작업을 순차적으로 수행하지 않고 동시 다발적으로 수행한다는 것이다. 또한 React의 큰 장점인 SPA로 동작 할 수 있는 이유도 비동기 때문 인것이다. 

완벽한 구조도는 아니지만 내가 이해한 추상적인 모습의 React와 동기,비동기의 모습니다.

개념은 이정도로 하고 이제 useState를 코드로 살펴보자. 기본적으로 우리는 useState는 변경된 값을 UI에도 변경을 해주는 코드라고 알고 있다. 그렇게 이해했다면 잘이해한 것이다. 단 우리는 여기서 어떻게 왜 useState를 통해서만 UI에 반영이 되는지를 이해야한다. 전 시간에 우리는 ReactDOM을 통해서 render를 했었다. 이것을 이용해서 UI에 count값이 변동되는 코드를 React CDN과 js로 확인해 볼 것이다. 코드는 아래와 같다.

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

<script type="text/babel">
  const root = document.getElementById("root")
  let counter = 0;

  function countUP() {
    counter = counter + 1;
    console.log(counter);
  };

  const Container = () => {
    return (
      <div>
        <h3>Total clicks: {counter}</h3>
        <button onClick={countUP}>Click me</button>
      </div>
    )
  };
  ReactDOM.render(<Container />, root);
</script>
 

이렇게 버튼을 통해서 값을 증가시킬 수 있게 코드를 작성 했다. 하지만 console.log로 확인했을 때는 잘 동작하지만 h3태그에 있는 counter 값은 변동되지 않는다 그 이유는 ReactDOM.render는 컴포넌트를 한번만 렌더링을 수행하기 때문이다. 결론적으로 클릭을 했을 때 ReactDOM을 다시 한번 랜더링을 해야하는 것이다. 그렇게 countUP함수를 수정한 코드는 아래와 같다.


  function countUP() {
    counter = counter + 1;
    console.log(counter);
    ReactDOM.render(<Container />, root);
  };

이렇게 작성하면 h3에 있는 count가 잘 변동되고 있는지 확인 할 수 있다. 여기서 왜 React가 효율적인지 확인 할 수 있다. 개발자 창을 열어보고 클릭했을 때 Vanilla Js로 클릭했을 때와 비교해보면 알 수 있다. 아래 더보기로 확인 할 수 있다.

더보기
&amp;amp;amp;amp;amp;amp;amp;lt;(왼)Vanilla JS / (오)React&amp;amp;amp;amp;amp;amp;amp;gt; UI가 변화, 렌더링되는 순간의 모습이다.

Vanilla JS로 작성한 코드는 span태그 전체가 렌더링이 되는 것이다. 하지만 React로 작성한 코드는 Number 값만 렌더링 되는 것을 알 수 있다.

 
코드가 조금 중복되는게 있어서 이를 좀 더 보기 좋게 바꾸면 아래와 같이 바꾸면 된다.

<script type="text/babel">
  const root = document.getElementById("root")
  let counter = 0;

  function countUP() {
    counter = counter + 1;
    console.log(counter);
    render(); //바뀐 데이터를 UI로 재랜더(rerender)
  };

  function render() {
    ReactDOM.render(<Container />, root);
  }

  const Container = () => {
    return (
      <div>
        <h3>Total clicks: {counter}</h3>   // DOM에 직접적이다. 기존 document보다 더욱 직접적이다.
        //전강의에서 했던 코드로 button을 클릭하고 해당 값이 변화될 수 있게 해볼 것.
        <button onClick={countUP}>Click me</button>
      </div>
    )
  };
  render();
</script>

보기엔 좋은 코드로 보인다. 하지만 코드가 매우 비효율적이라는 것을 알 수 있다. 연속적으로 렌더링을 한다는 것이다. 이를 해결 하기 위해서 useState를 사용하는 것이다. useState를 사용한 코드는 아래와 같다.

 
const root = document.getElementById('root');
 
  function App() {
    const [counter, setCounter] = React.useState(0); //rerender를 하는 구문을 내포하고 있다.
    const onClick = () => {
      setCounter(counter + 1);
    };
 
    return (
      <div>
        <h3>Total clicks: {counter}</h3>
        <button onClick={onClick}>Click me</button>
      </div>
    )
  };
  ReactDOM.render(<App />, root);
 

useState는 위에서 우리가 작성했던 render함수처럼 리렌더링하는 구문없이 상태를 관리하는 비동기 처리 함수이다. counter 에는 렌더링이 완료된 값을 가지고 있다. 즉, 다시말해 DOM에 반영된 직후의 결과를 가지고 있는 것이다. 하지만 여기서는 비동기적으로 처리되지만 비동기적인 결과물을 도출 할 수 없다. 그 이유는 현재 useState가 App컴포넌트 안에 존재하고 렌더링을 클릭과 동시에 되기 때문에 확인하기 어렵다.(useState는 비동기가 아니다. 다만 useState가  DOM에 반영된 직후의 결과를 가지고 온다는 것에 초점을 두어야 한다.) 마지막으로 useState의 특징적인 문법이 있다. 구조분해 할당으로 이루어 져 있다는 것이다. 두 번째 인자는 함수로 되어 있다는 것이고 구조 분해 할당의 예시는 아래 더보기를 눌러 확인하면 된다. 실제 작업하는 React 환경의 useState의 코드는 아래와 같고 결과의 이미지는 다음과 같다.

더보기
 
  const food = ['tomato', 'potato', 'banana', 'apple'];
  const [myFirst, mySecond, ...rest] = food;  //...rest는 나머지 배열 값들을 포함하고 있다.
 
  myFirst
  'tomato'
  mySecond
  'potato'
  rest
  (2) ['banana', 'apple']

 

import { useState } from "react"

export default function Test() {
  const [number, setNumber] = useState(0);

  return (
    <div>
      <h3>{number}</h3>
      <button onClick={() => { setNumber(number + 1); console.log(number) }}>click</button>
    </div>
  )
}
 
useState 코드의 UI와 console.log의 차이를 확인 할 수 있다.

이렇게 useState의 동작 원리와 비동기의 동작을 살펴 보았다. 비동기의 동작원리를 글로 접근해서 보면 굉장히 추상적이다. 그리고 이해가 잘 안갈 것이다. 현재 나도 내가 비동기를 잘 이해했다고 백퍼센트 확실 할 순 없지만 꾸준히 비동기와 관련된 글과 포스팅 강의를 보고 정리해온 지금까지의 비동기를 그림으로 코드로 구현한 것이다. 나중에 비동기에 관해서 새롭게 알게되거나 잘못된 정보가 있다면 따로 포스팅을 하거나 여기서 글을 수정하면서 업데이트 할 것이다.
* Reference

728x90
Comments