리액트 상태와 스냅샷

nearworld·2023년 1월 21일
0

리액트 기초

목록 보기
5/12
post-custom-banner

상태를 스냅샷으로 보기

리액트의 상태 변수가 그냥 일반적인 자바스크립트 변수처럼 보이지만 상태는 하나의 스냅샷에 더 가깝다. setState 함수를 이용하여 상태를 업데이트하는 것은 현재의 상태를 변경시키지 않고 리렌더링을 일으킨다.

이번 포스팅에서 얻을 수 있는 부분은..

  • 상태 값을 변경하는게 어떻게 리렌더링을 일으키는가?
  • 언제 그리고 어떻게 상태가 업데이트 되는가?
  • 왜 상태는 변경 즉시 업데이트되지 않는가?
  • 이벤트 핸들러가 어떻게 상태의 스냅샷에 접근하는가?

상태를 변경하는 것은 렌더링을 일으킨다.

유저가 웹페이지 상에서 마우스 클릭 이벤트를 일으키는 즉시 상태가 변경된다고 생각이 들 수도 있다. 하지만 리액트에서는 이런 Mental Model과는 다르게 동작한다.

렌더링시 스냅샷이 이용된다.

리액트에서 렌더링은 컴포넌트 함수를 호출하는 것을 의미한다.
컴포넌트 함수가 호출되면 JSX를 리턴하는데 이 리턴된 JSX가 UI 스냅샷이다. Props, 이벤트 핸들러, 지역 변수들이 렌더링 시점의 상태 값을 기반으로 처리된다.
사진이나 영화 장면과 다르게 컴포넌트 함수가 리턴한 UI 스냅샷은 인터랙티브하다. 유저 입력에 반응하는 이벤트 리스너 같은 요소들이 UI 스냅샷에 포함되어 있기 때문이다. 리액트는 이 스냅샷과 웹페이지 화면을 일치시키기 위해 화면을 업데이트하고 이벤트 핸들러들을 연결한다. 이런 과정을 통해 유저가 버튼을 클릭할 경우 이벤트 핸들러가 작동하게 된다.

리액트가 컴포넌트를 리렌더링할 때 생기는 일

  1. 리액트는 컴포넌트 함수를 다시 호출한다.
  2. 컴포넌트 함수는 새로운 JSX 스냅샷을 리턴한다.
  3. 리액트는 리턴된 JSX스냅샷에 화면이 일치하도록 화면을 업데이트한다.

컴포넌트 함수 호출 -> 스냅샷 처리 -> DOM 트리 업데이트
컴포넌트의 메모리 관점에서, 상태는 함수 리턴후 사라지는 일반적인 자바스크립트 변수같은게 아니다. 리액트에서 상태는 함수 외부인 리액트 그 자체에 존재하고 있다.
리액트가 컴포넌트 함수를 호출할 때, 그 특정한 렌더링 때의 상태를 스냅샷 찍는다.
컴포넌트 함수는 렌더링 시점의 상태 값을 사용하여 새로운 props와 이벤트 핸들러가 적용된 UI 스냅샷을 리턴한다.

setState 함수 호출 -> 상태 업데이트 -> 상태 값의 스냅샷이 컴포넌트로 보내진다.

이게 어떻게 작동하는지 확인하기 위한 코드를 보자.

import { useState } from 'react';

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

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber(number + 1);
        setNumber(number + 1);
        setNumber(number + 1);
      }}>+3</button>
    </>
  )
}

버튼 클릭 시 한 번에 이전 상태에 3이 더해진게 나타날거라 생각할 수도 있다. 하지만 코드를 실행해보면 1만 더해지는 것을 알 수 있다.

setNumber 함수가 하는 것은 버튼 클릭이 일어난 시점의 렌더링 단계에서 상태를 변경하는게 아니라 다음 리렌더링때 반영될 상태 값을 업데이트 하는 것이다.
버튼이 클릭된 시점의 렌더링 단계에서는 number 값이 0이기 때문에 이 렌더링 단계에서 아무리 setNumber 함수가 실행되어 상태 값 변경을 시도해도 현재 렌더링 시점의 상태값을 기반으로 +1된 결과만 다음 렌더링때 반영된다.

<button onClick={() => {
  setNumber(number + 1); // 다음 렌더링때 number 값을 1로 만들기 위해 준비.
  setNumber(number + 1); // 다음 렌더링때 number 값을 1로 만들기 위해 준비.
  setNumber(number + 1); // 다음 렌더링때 number 값을 1로 만들기 위해 준비.
}}>+3</button>

number 상태 변수가 평가된 상황은 아래와 같다.

<button onClick={() => {
  setNumber(0 + 1); // 다음 렌더링때 number 값을 1로 만들기 위해 준비.
  setNumber(0 + 1); // 다음 렌더링때 number 값을 1로 만들기 위해 준비.
  setNumber(0 + 1); // 다음 렌더링때 number 값을 1로 만들기 위해 준비.
}}>+3</button>

유저가 웹페이지에서 버튼을 클릭하면 onClick 이벤트 리스너에 등록된 콜백 함수가 호출될 것이고 이 콜백함수 내부의 코드가 모두 처리되면 리렌더링이 일어나게 된다.

import { useState } from 'react';

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

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber(number + 5);
        setTimeout(() => {
          alert(number);
        }, 3000);
      }}>+5</button>
    </>
  )
}

위 코드는 버튼 클릭 후 3초 뒤에 number 상태 값을 보여주는 alert 팝업이 나타나게 한다.
버튼을 클릭 후 이벤트 핸들러가 호출되어 이벤트 핸들러 내부의 코드가 처리되기 시작한다. 이때 setNumber 함수 호출로 인해 리액트는 다음 렌더링때 업데이트된 상태 값을 반영하기 위한 준비를 한다. 아직 리렌더링이 일어나지 않으므로 number값은 0이며 그 상황에서 setTimeout 코드가 처리되는데 이때 리렌더링이 일어나지 않았기에 number값은 여전히 0이다.

그래서 버튼을 클릭하면 이벤트 핸들러 코드가 처리되고 리렌더링이 일어나 화면에 5로 바뀐게 반영된다. 그리고 약 3초 뒤에 number값이 alert 팝업을 통해 출력되는데 이때 값은 0이다.
왜냐하면 alert(number) 코드가 처리되던 시점의 렌더링에서는 number값이 0이었기 때문이다. number값의 변경은 리렌더링후에 반영된다.

profile
깃허브: https://github.com/nearworld
post-custom-banner

0개의 댓글