React 15. State as a Snapshot

뚜루미·2024년 3월 21일

React

목록 보기
15/39
post-thumbnail

상태 변수는 읽고 쓸 수 있는 일반 JavaScript 변수처럼 보일 수 있습니다. 그러나 상태는 스냅샷처럼 동작합니다. 이를 설정하면 이미 가지고 있는 상태 변수가 변경되지 않고 대신 리렌더링이 트리거됩니다.

Setting state triggers renders

클릭과 같은 사용자 이벤트에 대한 응답으로 사용자 인터페이스가 직접 변경되는 것으로 생각할 수 있습니다. React에서는 약간 다르게 작동합니다. 이전 페이지에서 상태 설정이 React에서 리렌더링을 요청하고 이는 인터페이스가 이벤트에 반응하려면 상태를 업데이트 해야 함을 의미합니다.

이 예에서 “send”를 누르면 setIsSent(true) 가 React에 리렌더링을 전달합니다.

import { useState } from 'react';

export default function Form() {
  const [isSent, setIsSent] = useState(false);
  const [message, setMessage] = useState('Hi!');
  if (isSent) {
    return <h1>Your message is on its way!</h1>
  }
  return (
    <form onSubmit={(e) => {
      e.preventDefault();
      setIsSent(true);
      sendMessage(message);
    }}>
      <textarea
        placeholder="Message"
        value={message}
        onChange={e => setMessage(e.target.value)}
      />
      <button type="submit">Send</button>
    </form>
  );
}

function sendMessage(message) {
  // ...
}

버튼을 클릭하면 다음과 같은 일이 발생합니다.

  1. onsubmit 이벤트 핸들러가 실행됩니다.
  2. setIsSent(true)isSenttrue 로 설정하고 새로운 렌더링 대기열에 넣습니다.
  3. React는 새로운 isSent 값을 통하여 컴포넌트를 리렌더링합니다.

상태와 렌더링의 관계를 자세히 살펴보겠습니다.

Rendering takes a snapshot in time

“렌더링”은 React가 함수인 컴포넌트를 호출한다는 것을 의미합니다. 해당 함수에서 반환하는 JSX는 시간에 따른 UI의 스냅샷과 같습니다. props, 이벤트 핸들러 및 지역 변수는 모두 렌더링 당시의 상태를 사용하여 계산되었습니다.

사진이나 동영상 프레임과 달리 반환되는 UI “스냅샷”은 대화형입니다. 여기에는 입력에 대한 응답으로 발생하는 작업을 지정하는 이벤트 핸들러와 같은 논리가 포함됩니다. React는 이 스냅샷과 일치하도록 화면을 업데이트하고 이벤트 핸들러를 연결합니다. 결과적으로 버튼을 누르면 JSX에서 클릭 핸들러가 트리거 됩니다.

React가 컴포넌트를 다시 렌더링할 때:

  1. React는 함수를 다시 호출합니다.
  2. 함수는 새로운 JSX 스냅샷을 반환합니다.
  3. 그런 다음 React는 함수가 반환한 스냅샷과 일치하도록 화면을 업데이트합니다.

컴포넌트의 메모리로서 상태는 함수가 반환된 후 사라지는 일반 변수와는 다릅니다. 상태는 실제로 마치 선반위에 있는 것처럼 React 자체에 (함수 외부에) “살아 있습니다.” React가 컴포넌트를 호출하면 특정 렌더링 상태에 대한 스냅샷을 제공합니다. 컴포넌트는 JSX의 새로운 props 및 이벤트 핸들러 세트와 함께 UI의 스냅샷을 반환하며, 모두 해당 렌더링의 상태 값을 사용하여 계산됩니다.

이것이 어떻게 작동하는지 보여주는 작은 실험이 있습니다. 이 예에서는 "+3" 버튼을 클릭하면 setNumber(number + 1) 를 세 번 호출되므로 카운터가 세 번 증가할 것으로 예상할 수 있습니다.

“+3” 버튼을 클릭하면 어떤 일이 일어나는지 확인하세요.

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>
    </>
  )
}

number 클릭 당 1씩 증가합니다.

상태 설정은 다음 렌더링에 대해서만 변경됩니다. 첫 번째 렌더링 중에는 number가 0이었습니다. 이것은 onClick 핸들러의 해당 렌더링에서 setNumber(number + 1) 가 호출되었을 때 number의 값이 0인 이유입니다.

다음은 이 버튼의 onClick 핸들러가 React에게 수행하도록 지시하느 내용입니다.

  1. setNumber(number + 1)number 가 0이므로 setNumber(0 + 1)
    • React는 다음 렌더링에서 number 를 1로 바꿀 준비를 합니다.
  2. setNumber(number + 1)number 가 0이므로 setNumber(0 + 1)
    • React는 다음 렌더링에서 number 를 1로 바꿀 준비를 합니다.
  3. setNumber(number + 1)number 가 0이므로 setNumber(0 + 1)
    • React는 다음 렌더링에서 number 를 1로 바꿀 준비를 합니다.

setNumber(number + 1)을 세 번 호출했더라도 이 렌더링의 이벤트 핸들러는 number가 항상 0이므로 상태를 1로 세 번 설정합니다. 이것이 바로 이벤트 핸들러가 완료된 후 React가 number와 같은 컴포넌트를 1이 아닌 3으로 다시 렌더링하는 이유입니다.

// 초기 렌더링
<button onClick={() => {
  setNumber(0 + 1);
  setNumber(0 + 1);
  setNumber(0 + 1);
}}>+3</button>

// 두 번째 렌더링
<button onClick={() => {
  setNumber(1 + 1);
  setNumber(1 + 1);
  setNumber(1 + 1);
}}>+3</button>

이것 이바로 버튼을 다시 클릭하면 카운터가 2로 설정되고 그 다음 클릭에서는 3으로 설정되는 이유입니다.

State over time

버튼을 눌렀을 때 경고되는 내용을 예상해 볼 수 있습니다.

import { useState } from 'react';

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

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

이전의 방법을 사용하면 경고에 “0”이 표시되는 것으로 추측할 수 있습니다.

하지만 경고에 타이머를 설정하여 컴포넌트가 다신 렌더링된 후에 실행된다면 어떻게 될까요?

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>
    </>
  )
}

React에 저장된 상태는 알림이 실행될 때 변경되었을 수 있지만 사용자가 상호 작용할 때 상태의 스냅샷을 사용하여 예약되었습니다.

이벤트 핸들러의 코드가 비동기적이더라도 상태 변수의 값은 렌더링 내에서 절대 변경되지 않습니다. 해당 렌더링 내부에서 setNumber(number + 5) 이 호출된 후에도 onClick 값이 number0으로 계속 유지됩니다. React가 컴포넌트를 호출하여 UI의 “스냅샷”을 찍을 때 그 값은 “고정”되었습니다.

다음은 이벤트 핸들러의 타이밍 실수를 줄이는 방법에 대한 예입니다. 다음은 5초 지연하여 메시지를 보내는 양식입니다.

  1. “보내기”버튼을 누르면 “Hello”가 Alice에게 전송됩니다.
  2. 5초 지연이 끝나기 전에 “받는 사람” 필드의 값을 “Bob”으로 변경합니다.
import { useState } from 'react';

export default function Form() {
  const [to, setTo] = useState('Alice');
  const [message, setMessage] = useState('Hello');

  function handleSubmit(e) {
    e.preventDefault();
    setTimeout(() => {
      alert(`You said ${message} to ${to}`);
    }, 5000);
  }

  return (
    <form onSubmit={handleSubmit}>
      <label>
        To:{' '}
        <select
          value={to}
          onChange={e => setTo(e.target.value)}>
          <option value="Alice">Alice</option>
          <option value="Bob">Bob</option>
        </select>
      </label>
      <textarea
        placeholder="Message"
        value={message}
        onChange={e => setMessage(e.target.value)}
      />
      <button type="submit">Send</button>
    </form>
  );
}

React는 한 렌더링의 이벤트 핸들러 내에서 상태 값을 “고정”으로 유지합니다. 코드가 실행되는 동안 상태가 변경되지 않습니다.

0개의 댓글