React 공식 문서 스터디 : 2-5, 2-6

Junho Yun·2023년 4월 5일
0

state 업데이트 큐

목차

  • 일괄처리(배칭, batching)이란 무엇이며 React가 여러 state 업데이트를 처리하는 방법
  • 동일한 state 변수에서 여러 업데이트를 적용하는 방법

React batches state updates

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  function handleClick() {
    setCount(count + 1);
    setCount(count + 1);
    setCount(count + 1);
  }

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleClick}>Increment</button>
    </div>
  );
}

여기에는 useState를 사용하여 카운트 상태를 유지하는 Counter 구성 요소가 있습니다. handleClick 함수 내에서 count 상태를 빠르게 연속해서 세 번 업데이트합니다.

이제 'Increment' 버튼을 클릭하면 카운트가 3씩 증가할 것으로 예상되지만 실제로는 그렇지 않습니다. 대신 카운트는 1씩만 증가합니다. 이는 React가 상태 업데이트를 일괄 처리하기 때문에 발생하므로 setCount에 대한 세 번의 호출이 모두 단일 일괄 처리로 처리됩니다. setCount에 대한 각 호출은 업데이트된 값이 아니라 count의 이전 값을 기반으로 하므로 카운트는 1씩만 증가합니다.

이를 수정하기 위해 현재 상태 값을 인수로 받는 콜백 함수를 인수로 취하는 함수형 setCount를 사용할 수 있습니다. 그런 다음 이 현재 값을 사용하여 다음 상태 값을 계산할 수 있습니다.

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  function handleClick() {
    setCount((prevCount) => prevCount + 1);
    setCount((prevCount) => prevCount + 1);
    setCount((prevCount) => prevCount + 1);
  }

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleClick}>Increment</button>
    </div>
  );
}

왜 일괄처리?

일괄 처리를 통해 React는 각 업데이트 후 구성 요소를 렌더링하지 않고 한 번에 모든 상태 업데이트를 수행할 수 있습니다. 대신 상태 업데이트를 대기열에 넣은 다음 업데이트된 상태 값으로 구성 요소를 한 번 다시 렌더링합니다. 이렇게 하면 구성 요소를 다시 렌더링해야 하는 횟수가 줄어들어 성능이 크게 향상될 수 있습니다.

또한 DOM을 너무 자주 업데이트하는 잠재적인 문제를 방지하려면 일괄 처리가 필요합니다. DOM 업데이트는 비용이 많이 드는 작업일 수 있으며 여러 업데이트를 빠르게 연속적으로 수행하면 성능 문제와 시각적 결함이 발생할 수 있습니다. 상태 업데이트를 일괄 처리함으로써 React는 DOM 업데이트를 보다 효율적으로 수행하고 시각적 결함의 가능성을 줄일 수 있습니다.

Recap

  • state를 설정하더라도 기존 렌더링의 변수는 변경되지 않으며, 대신 새로운 렌더링을 요청합니다.
  • React는 이벤트 핸들러가 실행을 마친 후 state 업데이트를 처리합니다. 이를 일괄처리(배칭, batching)라고 합니다.
  • 하나의 이벤트에서 일부 state를 여러 번 업데이트하려면 setNumber(n => n + 1) 업데이터 함수를 사용할 수 있습니다.

Updating Objects in State

목차

  • React state에서 객체를 올바르게 업데이트하는 방법
  • 중첩된 객체를 변이하지 않고 업데이트하는 방법
  • 불변성이란 무엇이며, 불변성을 깨뜨리지 않는 방법
  • Immer로 객체 복사를 덜 반복적으로 만드는 방법

왓 이즈 뮤테이션 (What’s a mutation?)

React에서 뮤테이션은 상태 객체에 직접 적용된 변경 사항을 의미합니다. 업데이트된 값으로 새 상태 개체를 만들지 않고 상태 개체를 직접 수정할 때 변형이 발생할 수 있습니다.

React에서 뮤테이션은 예상치 못한 동작을 유발하고 상태의 변화를 추적하기 어렵게 만들 수 있기 때문에 문제가 됩니다. 상태를 직접 변경하면 React가 변경 사항을 감지하지 못할 수 있으며 이로 인해 가상 DOM과 실제 DOM 간에 불일치가 발생할 수 있습니다. 이로 인해 응용 프로그램에서 시각적 결함 또는 기타 예기치 않은 동작이 발생할 수 있습니다.

state is read only (로 사용하세요)

상태를 읽기 전용으로 취급한다는 것은 상태 객체를 직접 수정해서는 안 된다는 것을 의미합니다. 대신 업데이트된 값으로 새 상태 개체를 만들고 setState 함수를 사용하여 상태를 업데이트해야 합니다.

import React, { useState } from 'react';

function MyComponent() {
  const [person, setPerson] = useState({ name: 'John', age: 30 });

  function handleBirthday() {
    setPerson(prevPerson => ({ ...prevPerson, age: prevPerson.age + 1 }));
  }

  return (
    <div>
      <p>Name: {person.name}</p>
      <p>Age: {person.age}</p>
      <button onClick={handleBirthday}>Have a Birthday</button>
    </div>
  );
}

이 예에서는 useState 후크를 사용하여 { name: 'John', age: 30 }의 초기 값을 가진 상태 변수 person을 생성합니다. 사용자가 버튼을 클릭하면 업데이트된 값으로 새 상태 객체를 생성하여 person 상태를 업데이트하는 handleBirthday 함수를 호출합니다.

person 개체를 직접 변경하는 대신 확산 연산자(...)를 사용하여 이전 person 개체의 복사본을 만든 다음 age를 업데이트하여 업데이트된 값으로 새 상태 개체를 만듭니다. 새로운 값을 가진 속성. 이전 상태 개체를 인수로 받는 함수(prevPerson)를 사용하여 이 새 상태 개체를 setPerson 함수에 전달합니다.

이렇게 하면 상태가 변경되지 않고 업데이트되고 응용 프로그램에서 예기치 않은 동작을 방지하는 데 도움이 됩니다.

import React, { useState } from 'react';

function MyComponent() {
  const [person, setPerson] = useState({ name: 'John', age: 30 });

  function handleBirthday() {
    person.age += 1;
    setPerson(person);
  }

  return (
    <div>
      <p>Name: {person.name}</p>
      <p>Age: {person.age}</p>
      <button onClick={handleBirthday}>Have a Birthday</button>
    </div>
  );
}

이 예제에서는 person 객체의 age 속성을 직접 수정하여 업데이트한 다음 동일한 객체를 setPerson 함수에 전달합니다. 이는 상태를 읽기 전용으로 처리하지 않으며 애플리케이션에서 예기치 않은 동작을 유발할 수 있습니다.

상태 개체를 직접 수정하면 React가 변경 사항을 감지하지 못할 수 있으며 이로 인해 가상 DOM과 실제 DOM 간에 불일치가 발생할 수 있습니다. 이로 인해 응용 프로그램에서 시각적 결함 또는 기타 예기치 않은 동작이 발생할 수 있습니다.

따라서 항상 상태를 읽기 전용으로 취급하고 기존 상태 개체를 직접 수정하는 대신 업데이트된 값으로 새 상태 개체를 만드는 것이 중요합니다.

지역 변이는 괜찮아요

const nextPosition = {};
nextPosition.x = e.clientX;
nextPosition.y = e.clientY;
setPosition(nextPosition);

위와 같은 코드는 괜찮습니다.

여러 필드에 단일 이벤트 핸들러 사용하기

객체 내에서 [ 및 ] 중괄호를 사용하여 동적 이름을 가진 프로퍼티를 지정할 수도 있습니다. 다음은 동일한 예시이지만 세 개의 다른 이벤트 핸들러 대신 단일 이벤트 핸들러를 사용한 예시입니다:

import { useState } from 'react';

export default function Form() {
  const [person, setPerson] = useState({
    firstName: 'Barbara',
    lastName: 'Hepworth',
    email: 'bhepworth@sculpture.com'
  });

  function handleChange(e) {
    setPerson({
      ...person,
      [e.target.name]: e.target.value
    });
  }

  return (
    <>
      <label>
        First name:
        <input
          name="firstName"
          value={person.firstName}
          onChange={handleChange}
        />
      </label>
      <label>
        Last name:
        <input
          name="lastName"
          value={person.lastName}
          onChange={handleChange}
        />
      </label>
      <label>
        Email:
        <input
          name="email"
          value={person.email}
          onChange={handleChange}
        />
      </label>
      <p>
        {person.firstName}{' '}
        {person.lastName}{' '}
        ({person.email})
      </p>
    </>
  );
}

중첩 객체 업데이트하기

import { useState } from 'react';

export default function Form() {
  const [person, setPerson] = useState({
    name: 'Niki de Saint Phalle',
    artwork: {
      title: 'Blue Nana',
      city: 'Hamburg',
      image: 'https://i.imgur.com/Sd1AgUOm.jpg',
    }
  });

  function handleNameChange(e) {
    setPerson({
      ...person,
      name: e.target.value
    });
  }

  function handleTitleChange(e) {
    setPerson({
      ...person,
      artwork: {
        ...person.artwork,
        title: e.target.value
      }
    });
  }

  function handleCityChange(e) {
    setPerson({
      ...person,
      artwork: {
        ...person.artwork,
        city: e.target.value
      }
    });
  }

  function handleImageChange(e) {
    setPerson({
      ...person,
      artwork: {
        ...person.artwork,
        image: e.target.value
      }
    });
  }

  return (
    <>
      <label>
        Name:
        <input
          value={person.name}
          onChange={handleNameChange}
        />
      </label>
      <label>
        Title:
        <input
          value={person.artwork.title}
          onChange={handleTitleChange}
        />
      </label>
      <label>
        City:
        <input
          value={person.artwork.city}
          onChange={handleCityChange}
        />
      </label>
      <label>
        Image:
        <input
          value={person.artwork.image}
          onChange={handleImageChange}
        />
      </label>
      <p>
        <i>{person.artwork.title}</i>
        {' by '}
        {person.name}
        <br />
        (located in {person.artwork.city})
      </p>
      <img 
        src={person.artwork.image} 
        alt={person.artwork.title}
      />
    </>
  );
}

중첩 객체란 사실 없다

let obj = {
  name: 'Niki de Saint Phalle',
  artwork: {
    title: 'Blue Nana',
    city: 'Hamburg',
    image: 'https://i.imgur.com/Sd1AgUOm.jpg',
  }
};

실제로는 아래처럼 두개의 객체가 있는 것이고, 하나의 객체 안에서 다른 객체를 참조하고 있는 형태입니다.

let obj1 = {
  title: 'Blue Nana',
  city: 'Hamburg',
  image: 'https://i.imgur.com/Sd1AgUOm.jpg',
};

let obj2 = {
  name: 'Niki de Saint Phalle',
  artwork: obj1
};

리캡

  • React의 모든 state를 불변으로 취급하세요.
  • state에 객체를 저장하면 객체를 변이해도 렌더링이 트리거되지 않고 이전 렌더링 "스냅샷"의 state가 변경됩니다.
  • 객체를 변이하는 대신 객체의 새로운 버전을 생성하고 state를 설정하여 다시 렌더링을 트리거하세요.
  • 객체 스프레드 문법 {...obj, something: 'newValue'}를 사용하여 객체 사본을 만들 수 있습니다.
  • 스프레드 문법은 한 수준 깊이만 복사하는 얕은 구문입니다.
  • 중첩된 객체를 업데이트하려면 업데이트하려는 위치에서 가장 위쪽까지 복사본을 만들어야 합니다.
  • 반복적인 코드 복사를 줄이려면 Immer를 사용하세요.
profile
의미 없는 코드는 없다.

0개의 댓글