React/공식 문서

불변 업데이트 패턴

favor 2026. 4. 28. 14:22

객체 State 업데이트하기

State는 객체를 포함한 모든 종류의 자바스크립트 값을 가질 수 있음
ㄴ React state가 가진 객체를 직접 변경해서는 안 됨
--> 객체를 업데이트하고 싶을 때에는 새로운 객체를 생성하여 (또는 기존 객체의 복사본을 만들어) 사용

 

✔️ 변경이란?

State에는 모든 종류의 자바스크립트 값을 저장 가능

ㄴ 이러한 자바스크립트 값들은 변경할 수 없거나, 읽기 전용을 의미하는 불변성을 가짐

 

const [x, setX] = useState(0);

 

값을 교체하기 위해서는 리렌더링이 필요

 

setX(5);

^ X state는 0에서 5로 바뀌었지만, 숫자 0 자체는 바뀌지 않았음

== x가 가리키는 대상(상태)은 바뀌었지만, 메모리에 존재하는 0이라는 숫자 값 그 자체가 5로 변한 것은 아님 

ㄴ 숫자, 문자열, 불리언과 같이 자바스크립트에 정의되어 있는 원시 값들은 변경할 수 없음 

 

기술적으로 객체 자체의 내용은 바꿀 수 있음

== 변경(mutation)

const [position, setPosition] = useState({ x: 0, y: 0});

position.x = 5;

 

* React state의 객체들이 기술적으로 변경 가능할지라도, 불변성을 가진 것처럼 다루어야 함

--> 객체를 변경하는 대신 교체해야 함

 

✔️ State를 읽기 전용인 것처럼 다루기

state에 저장한 자바스크립트 객체는 어떤 것이라도 읽기 전용인 것처럼 다루어야 함

 

// App.js

import { useState } from 'react';

export default function MovingDot() {
  const [position, setPosition] = useState({
    x: 0,
    y: 0
  });
  return (
    <div
      onPointerMove={e => {
        position.x = e.clientX;
        position.y = e.clientY;
      }}
      style={{
        position: 'relative',
        width: '100vw',
        height: '100vh',
      }}>
      <div style={{
        position: 'absolute',
        backgroundColor: 'red',
        borderRadius: '50%',
        transform: `translate(${position.x}px, ${position.y}px)`,
        left: -10,
        top: -10,
        width: 20,
        height: 20,
      }} />
    </div>
  )
}
onPointerMove={e => {
  position.x = e.clientX;
  position.y = e.clientY;
}}

^ position에 할당된 객체를 이전 렌더링에서 수정

그러나 React는 state 설정 함수가 없으면 객체가 변경되었는지 알 수 없음

--> React는 아무것도 하지 않음

 

리렌더링을 발생시키기 위해서는, 새 객체를 생성하여 state 설정 함수로 전달해야 함

onPointerMove={e => {
  setPosition({
    x: e.clientX,
    y: e.clientY
  });
}}

 

setPosition은 React에게 다음과 같이 요청

  • position을 이 새로운 객체로 교체하라
  • 그리고 이 컴포넌트를 다시 렌더링하라

 

✔️ 전개 문법으로 객체 복사하기

// App.js

import { useState } from 'react';

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

  function handleFirstNameChange(e) {
    person.firstName = e.target.value;
  }

  function handleLastNameChange(e) {
    person.lastName = e.target.value;
  }

  function handleEmailChange(e) {
    person.email = e.target.value;
  }

  return (
    <>
      <label>
        First name:
        <input
          value={person.firstName}
          onChange={handleFirstNameChange}
        />
      </label>
      <label>
        Last name:
        <input
          value={person.lastName}
          onChange={handleLastNameChange}
        />
      </label>
      <label>
        Email:
        <input
          value={person.email}
          onChange={handleEmailChange}
        />
      </label>
      <p>
        {person.firstName}{' '}
        {person.lastName}{' '}
        ({person.email})
      </p>
    </>
  );
}
person.firstName = e.target.value;

^ 이전 렌더의 state를 변경

 

원하는 동작을 정확히 얻기 위해서는 새로운 객체를 생성하여 setPerson으로 전달해야 함

단 하나의 필드가 바뀌었기 때문에 기존에 존재하는 다른 데이터를 복사해야 함

setPerson({
  firstName: e.target.value, // input의 새로운 first name
  lastName: person.lastName,
  email: person.email
});

 

... 객체 전개 문구를 사용하면 모든 프로퍼티를 각각 복사하지 않아도 됨

setPerson({
  ...person, // 이전 필드를 복사
  firstName: e.target.value // 새로운 부분은 덮어쓰기
});

 

✔️ 중첩된 객체 갱신하기

const [person, setPerson] = useState({
  name: 'Niki de Saint Phalle',
  artwork: {
    title: 'Blue Nana',
    city: 'Hamburg',
    image: 'https://react.dev/images/docs/scientists/Sd1AgUOm.jpg',
  }
});

 

person.artwork.city를 업데이트 하고 싶다면?

 

1. 새로운 artwork 객체를 생성한 뒤, 그것을 가리키는 새로운 person 객체를 만들기

const nextArtwork = { ...person.artwork, city: 'New Delhi' };
const nextPerson = { ...person, artwork: nextArtwork };
setPerson(nextPerson);

 

2. 단순하게 함수 호출

setPerson({
  ...person, // 다른 필드 복사
  artwork: { // artwork 교체
    ...person.artwork, // 동일한 값 사용
    city: 'New Delhi' // 하지만 New Delhi!
  }
});

 

✔️ 객체들은 사실 중첩되어 있지 않음

let obj = {
  name: 'Niki de Saint Phalle',
  artwork: {
    title: 'Blue Nana',
    city: 'Hamburg',
    image: 'https://react.dev/images/docs/scientists/Sd1AgUOm.jpg',
  }
};

^ 이러한 객체는 코드에서 중첩되어 나타남

 

사실 코드가 실행될 때, 중첩된 객체라는 것은 없음

우리는 두 개의 다른 객체를 보는 것

 

let obj1 = {
  title: 'Blue Nana',
  city: 'Hamburg',
  image: 'https://react.dev/images/docs/scientists/Sd1AgUOm.jpg',
};

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

 ^ obj1 객체는 obj2 안에 존재하는 것이 아님

 

let obj1 = {
  title: 'Blue Nana',
  city: 'Hamburg',
  image: 'https://react.dev/images/docs/scientists/Sd1AgUOm.jpg',
};

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

let obj3 = {
  name: 'Copycat',
  artwork: obj1
};

obj3.artwork.city를 변경하려 했다면, obj2.artwork.city와 obj1.city 둘 모두에게 영향을 미치게 됨

--> obj3.artwork, obj2.artwork와 obj1이 같은 객체이기 때문

 

∴ 객체는 중첩된 것이 아니라, 프로퍼티를 통해 서로를 가리키는 것임 

 

✔️ Immer로 간결한 갱신 로직 작성하기

state가 깊이 중첩되어 있다면 평탄화를 고려

ㄴ state 구조를 바꾸고 싶지 않다면, 중첩 전개할 수 있는 Immer가 있음

 

Immer : 변경 구문을 사용할 수 있게 해주며 복사본 생성을 도와주는 라이브러리

 

Immer 사용 방법

  1. package.json에 dependencies로 use-immer를 추가
  2. npm install 명령어 실행
  3. import { useState } from 'react'를 import { useImmer } from 'use-immer'로 교체
// App.js

import { useImmer } from 'use-immer';

export default function Form() {
  const [person, updatePerson] = useImmer({ // useImmer
    name: 'Niki de Saint Phalle',
    artwork: {
      title: 'Blue Nana',
      city: 'Hamburg',
      image: 'https://react.dev/images/docs/scientists/Sd1AgUOm.jpg',
    }
  });

  /*
  function handleNameChange(e) {
    setPerson({
      ...person,
      name: e.target.value
    });
  }
  */
  
  function handleNameChange(e) { 
    updatePerson(draft => { // setPerson() 대신 사용
      draft.name = e.target.value;
    });
  }
  
  /*
  function handleTitleChange(e) {
    setPerson({
      ...person,
      artwork: {
        ...person.artwork,
        title: e.target.value
      }
    });
  }
  */

  function handleTitleChange(e) {
    updatePerson(draft => {
      draft.artwork.title = e.target.value;
    });
  }
  
  /*
  function handleCityChange(e) {
    setPerson({
      ...person,
      artwork: {
        ...person.artwork,
        city: e.target.value
      }
    });
  }
  */

  function handleCityChange(e) {
    updatePerson(draft => {
      draft.artwork.city = e.target.value;
    });
  }
  
  /*
  function handleImageChange(e) {
    setPerson({
      ...person,
      artwork: {
        ...person.artwork,
        image: e.target.value
      }
    });
  }
  */

  function handleImageChange(e) {
    updatePerson(draft => {
      draft.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}
      />
    </>
  );
}

 

Immer는 업데이트 핸들러를 간결하게 관리 가능

특히 state가 중첩되어 있고 객체를 중복으로 복사해야 할 때 유용

 

 

 


배열 State 업데이트하기 

배열은 자바스크립트에서는 변경이 가능하지만, state로 저장할 때에는 변경할 수 없도록 처리해야 함
state에 저장된 배열을 업데이트 하고 싶을 때에는 새로운 배열을 생성하여 (또는 기존 배열의 복사본을 만들어) 사용

 

✔️ 변경하지 않고 배열 업데이트하기

자바스크립트에서 배열은 다른 종류의 객체

--> 객체와 마찬가지로 React state에서 배열은 읽기 전용으로 처리해야 함

ㄴ arr[0] = 'bird'처럼 배열 내부의 항목을 재할당하거나, push()나 pop() 같은 함수로 배열을 변경해서는 안 됨 

 

배열을 업데이트할 때마다 새 배열을 state 설정 함수에 전달해야 함

ㄴ state의 원본 배열을 변경시키지 않는 filter()map() 같은 함수를 사용하여 원본 배열로부터 새 배열을 만들 수 있음 

 

  비선호 (배열을 변경) 선호 (새 배열을 반환)
추가 push, unshift concat, [...arr] 전개 연산자
제거 pop, shift, splice filter, slice
교체 splice, arr[i]=... 할당 map
정렬 reverse, sort 배열을 복사한 이후 처리 

 

* slice와 splice 함수

  • slice를 사용하면 배열 또는 그 일부를 복사할 수 있음
  • splice는 배열을 변경함 
    • 항목을 추가하거나 제거 

 

✔️ 배열에 항목 추가하기

push()는 배열을 변경

--> 권장하지 않음

 

기존에 존재하던 항목들 뒤에 새 항목을 포함하는 새로운 배열을 만들기

--> ... 배열 전개 구문을 사용

 

// App.js

import { useState } from 'react';

let nextId = 0;

export default function List() {
  const [name, setName] = useState('');
  const [artists, setArtists] = useState([]);

  return (
    <>
      <h1>Inspiring sculptors:</h1>
      <input
        value={name}
        onChange={e => setName(e.target.value)}
      />
      <button onClick={() => {
        setArtists([
          ...artists,
          { id: nextId++, name: name }
        ]);
      }}>Add</button>
      <ul>
        {artists.map(artist => (
          <li key={artist.id}>{artist.name}</li>
        ))}
      </ul>
    </>
  );
}
setArtists( // 아래의 새로운 배열로 state를 변경
  [
    ...artists, // 기존 배열의 모든 항목에,
    { id: nextId++, name: name } // 마지막에 새 항목을 추가
  ]
);

 

✔️ 배열에서 항목 제거하기

해당 항목을 포함하지 않는 새 배열을 만들기

--> filter 함수를 사용

 

// App.js

import { useState } from 'react';

let initialArtists = [
  { id: 0, name: 'Marta Colvin Andrade' },
  { id: 1, name: 'Lamidi Olonade Fakeye'},
  { id: 2, name: 'Louise Nevelson'},
];

export default function List() {
  const [artists, setArtists] = useState(
    initialArtists
  );

  return (
    <>
      <h1>Inspiring sculptors:</h1>
      <ul>
        {artists.map(artist => (
          <li key={artist.id}>
            {artist.name}{' '}
            <button onClick={() => {
              setArtists(
                artists.filter(a =>
                  a.id !== artist.id
                )
              );
            }}>
              Delete
            </button>
          </li>
        ))}
      </ul>
    </>
  );
}
setArtists(
  artists.filter(a => a.id !== artist.id)
);

^ artist.id와 id가 다른 artists로 구성된 배열을 생성한다는 의미

 

* filter가 원본 배열을 수정하지는 않음

 

✔️ 배열 반환하기

배열의 일부 또는 전체 항목을 변경하여 새 배열을 만들기

--> map()을 사용

 

// App.js

import { useState } from 'react';

let initialShapes = [
  { id: 0, type: 'circle', x: 50, y: 100 },
  { id: 1, type: 'square', x: 150, y: 100 },
  { id: 2, type: 'circle', x: 250, y: 100 },
];

export default function ShapeEditor() {
  const [shapes, setShapes] = useState(
    initialShapes
  );

  function handleClick() {
    const nextShapes = shapes.map(shape => {
      if (shape.type === 'square') {
        // 변경시키지 않고 반환
        return shape;
      } else {
        // 50px 아래로 이동한 새로운 원을 반환
        return {
          ...shape,
          y: shape.y + 50,
        };
      }
    });
    // 새로운 배열로 리렌더링
    setShapes(nextShapes);
  }

  return (
    <>
      <button onClick={handleClick}>
        Move circles down!
      </button>
      {shapes.map(shape => (
        <div style={{
          background: 'purple',
          position: 'absolute',
          left: shape.x,
          top: shape.y,
          borderRadius:
            shape.type === 'circle'
              ? '50%' : '',
          width: 20,
          height: 20,
        }} />
      ))}
    </>
  );
}

 

✔️ 배열 내 항목 교체하기

배열에서 하나 이상의 항목을 교체하는 경우가 흔하지만,

arr[0] = 'bird'와 같은 할당은 원본 배열을 변경시키므로 map()을 사용하는 편이 좋음 

 

// App.js

import { useState } from 'react';

let initialCounters = [
  0, 0, 0
];

export default function CounterList() {
  const [counters, setCounters] = useState(
    initialCounters
  );

  function handleIncrementClick(index) {
    const nextCounters = counters.map((c, i) => {
      if (i === index) {
        // 클릭된 counter를 증가시킴
        return c + 1;
      } else {
        // 변경되지 않은 나머지를 반환
        return c;
      }
    });
    setCounters(nextCounters);
  }

  return (
    <ul>
      {counters.map((counter, i) => (
        <li key={i}>
          {counter}
          <button onClick={() => {
            handleIncrementClick(i);
          }}>+1</button>
        </li>
      ))}
    </ul>
  );
}

 

✔️ 배열에 항목 삽입하기

시작도, 끝도 아닌 위치에 항목을 삽입하기

--> ... 배열 전개 구문slice() 함수를 함께 사용

 

slice() 함수를 사용하면 배열의 일부분을 잘라낼 수 있음

삽입 지점 앞에 자른 배열을 전개하고, 새 항목과 원본 배열의 나머지 부분을 전개하는 배열을 만듦

 

// App.js

import { useState } from 'react';

let nextId = 3;
const initialArtists = [
  { id: 0, name: 'Marta Colvin Andrade' },
  { id: 1, name: 'Lamidi Olonade Fakeye'},
  { id: 2, name: 'Louise Nevelson'},
];

export default function List() {
  const [name, setName] = useState('');
  const [artists, setArtists] = useState(
    initialArtists
  );

  function handleClick() {
    const insertAt = 1; // 모든 인덱스가 될 수 있음
    const nextArtists = [
      // 삽입 지점 이전 항목
      ...artists.slice(0, insertAt),
      // 새 항목
      { id: nextId++, name: name },
      // 삽입 지점 이후 항목
      ...artists.slice(insertAt)
    ];
    setArtists(nextArtists);
    setName('');
  }

  return (
    <>
      <h1>Inspiring sculptors:</h1>
      <input
        value={name}
        onChange={e => setName(e.target.value)}
      />
      <button onClick={handleClick}>
        Insert
      </button>
      <ul>
        {artists.map(artist => (
          <li key={artist.id}>{artist.name}</li>
        ))}
      </ul>
    </>
  );
}

 

✔️ 배열에 기타 변경 적용하기

자바스크립트의 reverse() 및 sort() 함수는 원본 배열을 변경

--> 직접 사용하는 것은 권장하지 않음 

 

먼저 배열을 복사한 뒤 변경 가능 

 

// App.js

import { useState } from 'react';

const initialList = [
  { id: 0, title: 'Big Bellies' },
  { id: 1, title: 'Lunar Landscape' },
  { id: 2, title: 'Terracotta Army' },
];

export default function List() {
  const [list, setList] = useState(initialList);

  function handleClick() {
    const nextList = [...list];
    nextList.reverse();
    setList(nextList);
  }

  return (
    <>
      <button onClick={handleClick}>
        Reverse
      </button>
      <ul>
        {list.map(artwork => (
          <li key={artwork.id}>{artwork.title}</li>
        ))}
      </ul>
    </>
  );
}

^ [...list] 전개 구문을 사용해 원본 배열의 복사본을 만듦

복사본이 있으므로 nextList.reverse() 또는 nextList.sort()와 같은 변경 함수를 사용하거나 nextList[0] = "something"과 같이 개별 항목 할당 가능

 

* 배열을 복사하더라도 배열 내부에 기존 항목을 직접 변경해서는 안 됨 

ㄴ 얕은 복사이기 때문에 복사한 새 배열에는 원본 배열과 동일한 항목이 포함됨

--> 복사된 배열 내부의 객체를 수정하면 기존 state가 변경됨  

 

const nextList = [...list];
nextList[0].seen = true; // 문제: list[0]을 변경시킴
setList(nextList);

^ nextList와 list는 서로 다른 배열이지만, nextList[0]과 list[0]은 동일한 객체를 가리킴

--> nextList[0].seen을 변경하면 list[0].seen도 변경됨

 

✔️ 배열 내부의 객체 업데이트하기

객체는 실제로 배열 내부에 위치해있지 않음

코드에서는 내부로 나타날 수 있지만, 배열의 각 객체는 배열이 가리키는 별도의 값

 

중첩된 state를 업데이트할 때, 업데이트하려는 지점부터 최상위 레벨까지의 복사본을 만들어야 함

 

// App.js

import { useState } from 'react';

let nextId = 3;
const initialList = [
  { id: 0, title: 'Big Bellies', seen: false },
  { id: 1, title: 'Lunar Landscape', seen: false },
  { id: 2, title: 'Terracotta Army', seen: true },
];

export default function BucketList() {
  const [myList, setMyList] = useState(initialList);
  const [yourList, setYourList] = useState(
    initialList
  );

  function handleToggleMyList(artworkId, nextSeen) {
    const myNextList = [...myList];
    const artwork = myNextList.find(
      a => a.id === artworkId
    );
    artwork.seen = nextSeen;
    setMyList(myNextList);
  }

  function handleToggleYourList(artworkId, nextSeen) {
    const yourNextList = [...yourList];
    const artwork = yourNextList.find(
      a => a.id === artworkId
    );
    artwork.seen = nextSeen;
    setYourList(yourNextList);
  }

  return (
    <>
      <h1>Art Bucket List</h1>
      <h2>My list of art to see:</h2>
      <ItemList
        artworks={myList}
        onToggle={handleToggleMyList} />
      <h2>Your list of art to see:</h2>
      <ItemList
        artworks={yourList}
        onToggle={handleToggleYourList} />
    </>
  );
}

function ItemList({ artworks, onToggle }) {
  return (
    <ul>
      {artworks.map(artwork => (
        <li key={artwork.id}>
          <label>
            <input
              type="checkbox"
              checked={artwork.seen}
              onChange={e => {
                onToggle(
                  artwork.id,
                  e.target.checked
                );
              }}
            />
            {artwork.title}
          </label>
        </li>
      ))}
    </ul>
  );
}
const myNextList = [...myList];
const artwork = myNextList.find(a => a.id === artworkId);
artwork.seen = nextSeen; // 문제: 기존 항목을 변경시킵니다.
setMyList(myNextList);

^ myNextList 배열 자체는 새로운 배열이지만, 항목 자체는 myList 원본 배열과 동일 

--> artwork.seen을 변경하면 원본 artwork 항목이 변경됨

ㄴ 해당 artwork 항목은 yourArtWorks에도 존재하므로 버그가 발생함 

 

* map을 사용하면 이전 항목의 변경 없이 업데이트된 버전으로 대체 가능 

 

// App.js

import { useState } from 'react';

let nextId = 3;
const initialList = [
  { id: 0, title: 'Big Bellies', seen: false },
  { id: 1, title: 'Lunar Landscape', seen: false },
  { id: 2, title: 'Terracotta Army', seen: true },
];

export default function BucketList() {
  const [myList, setMyList] = useState(initialList);
  const [yourList, setYourList] = useState(
    initialList
  );

  function handleToggleMyList(artworkId, nextSeen) {
    setMyList(myList.map(artwork => {
      if (artwork.id === artworkId) {
        // 변경된 *새* 객체를 만들어 반환합니다.
        return { ...artwork, seen: nextSeen };
      } else {
        // 변경시키지 않고 반환합니다.
        return artwork;
      }
    }));
  }

  function handleToggleYourList(artworkId, nextSeen) {
    setYourList(yourList.map(artwork => {
      if (artwork.id === artworkId) {
        // 변경된 *새* 객체를 만들어 반환합니다.
        return { ...artwork, seen: nextSeen };
      } else {
        // 변경시키지 않고 반환합니다.
        return artwork;
      }
    }));
  }

  return (
    <>
      <h1>Art Bucket List</h1>
      <h2>My list of art to see:</h2>
      <ItemList
        artworks={myList}
        onToggle={handleToggleMyList} />
      <h2>Your list of art to see:</h2>
      <ItemList
        artworks={yourList}
        onToggle={handleToggleYourList} />
    </>
  );
}

function ItemList({ artworks, onToggle }) {
  return (
    <ul>
      {artworks.map(artwork => (
        <li key={artwork.id}>
          <label>
            <input
              type="checkbox"
              checked={artwork.seen}
              onChange={e => {
                onToggle(
                  artwork.id,
                  e.target.checked
                );
              }}
            />
            {artwork.title}
          </label>
        </li>
      ))}
    </ul>
  );
}
setMyList(myList.map(artwork => {
  if (artwork.id === artworkId) {
    // 변경된 *새* 객체를 만들어 반환
    return { ...artwork, seen: nextSeen };
  } else {
    // 변경시키지 않고 반환
    return artwork;
  }
}));

 

✔️ Immer로 간결한 업데이트 로직 작성하기

변경 없이 중첩된 배열을 업데이트하는 것은 객체와 마찬가지로 반복적일 수 있음

  • 일반적으로 깊은 레벨까지의 state를 업데이트할 필요는 없음
    • state 객체가 매우 깊다면 다르게 재구성하여 평평하게 만들 수 있음
  • state 구조를 변경하고 싶지 않다면 Immer을 사용할 수 있음
    • 손쉽게 변경 문법을 사용하여 작성할 수 있고 복사본을 생성하여 처리 가능 

 

// App.js

import { useState } from 'react';
import { useImmer } from 'use-immer';

let nextId = 3;
const initialList = [
  { id: 0, title: 'Big Bellies', seen: false },
  { id: 1, title: 'Lunar Landscape', seen: false },
  { id: 2, title: 'Terracotta Army', seen: true },
];

export default function BucketList() {
  const [myList, updateMyList] = useImmer(
    initialList
  );
  const [yourArtworks, updateYourList] = useImmer(
    initialList
  );

  function handleToggleMyList(id, nextSeen) {
    updateMyList(draft => {
      const artwork = draft.find(a =>
        a.id === id
      );
      artwork.seen = nextSeen;
    });
  }

  function handleToggleYourList(artworkId, nextSeen) {
    updateYourList(draft => {
      const artwork = draft.find(a =>
        a.id === artworkId
      );
      artwork.seen = nextSeen;
    });
  }

  return (
    <>
      <h1>Art Bucket List</h1>
      <h2>My list of art to see:</h2>
      <ItemList
        artworks={myList}
        onToggle={handleToggleMyList} />
      <h2>Your list of art to see:</h2>
      <ItemList
        artworks={yourArtworks}
        onToggle={handleToggleYourList} />
    </>
  );
}

function ItemList({ artworks, onToggle }) {
  return (
    <ul>
      {artworks.map(artwork => (
        <li key={artwork.id}>
          <label>
            <input
              type="checkbox"
              checked={artwork.seen}
              onChange={e => {
                onToggle(
                  artwork.id,
                  e.target.checked
                );
              }}
            />
            {artwork.title}
          </label>
        </li>
      ))}
    </ul>
  );
}
updateMyTodos(draft => {
  const artwork = draft.find(a => a.id === artworkId);
  artwork.seen = nextSeen;
});

^ 원본 state를 변경하는 것이 아니라, Immer에서 제공하는 특수 draft 객체를 변경함

 

내부적으로 Immer는 항상 draft에서 수행한 변경 사항에 따라 처음부터 다음 state를 구성함

 

'React > 공식 문서' 카테고리의 다른 글

useReducer  (0) 2026.05.21
상태 구조 설계와 공유  (0) 2026.05.07
렌더링과 State 업데이트  (0) 2026.04.08
이벤트 처리와 State  (0) 2026.04.02
Props와 리스트 렌더링  (0) 2026.03.25