Arrays are mutable in JavaScript, but you should treat them as immutable when you store them in state. Just like with objects, when you want to update an array stored in state, you need to create a new one (or make a copy of an existing one), and then set state to use the new array. JavaScript에서 배열은 변경 가능하지만 state에 저장할 때는 변경이 불가능한 것으로 취급해야합니다. 객체와 마찬가지로, state에 저장된 배열을 업데이트하려면, 새로운 배열을 만들고(또는 기존 배열을 복사본을 만듦) 새 배열을 사용하도록 state를 설정해야합니다.

You will learn학습 내용

  • How to add, remove, or change items in an array in React state
  • How to update an object inside of an array
  • How to make array copying less repetitive with Immer
  • React state 안의 배열 항목을 추가, 제거 또는 변경하는 방법
  • 배열 내부의 객체를 업데이트 하는 방법
  • Immer로 덜 반복적으로 배열을 복사하는 방법

Updating arrays without mutation변이 없이 배열 업데이트하기

In JavaScript, arrays are just another kind of object. Like with objects, you should treat arrays in React state as read-only. This means that you shouldn’t reassign items inside an array like arr[0] = 'bird', and you also shouldn’t use methods that mutate the array, such as push() and pop(). JavaScript에서 배열은 객체의 또 다른 종류일 뿐입니다. 객체와 마찬가지로, React state의 배열은 읽기 전용으로 취급해야 합니다. 즉, arr[0] = 'bird'와 같이 배열 내부의 항목을 재할당해서는 안 되며, push()pop()과 같이 배열을 변이하는 메서드도 사용해서는 안 됩니다.

Instead, every time you want to update an array, you’ll want to pass a new array to your state setting function. To do that, you can create a new array from the original array in your state by calling its non-mutating methods like filter() and map(). Then you can set your state to the resulting new array. 대신 배열을 업데이트할 때마다 state 설정 함수에 새 배열을 전달하고 싶을 것입니다. 그렇게 하려면 state의 원래 배열에서 filter()map()과 같은 비변이 메서드를 호출하여 새 배열을 만들면 됩니다. 그렇게 만들어진 새 배열을 state로 설정할 수 있습니다.

Here is a reference table of common array operations. When dealing with arrays inside React state, you will need to avoid the methods in the left column, and instead prefer the methods in the right column: 다음은 일반적인 배열 연산에 대한 참조 표입니다. React state 내에서 배열을 다룰 때는 왼쪽 열의 메서드를 피하고 대신 오른쪽 열의 메서드를 선호해야 합니다:

avoid (mutates the array)
비추천 (배열 직접 변이)
prefer (returns a new array)
추천 (새 배열 반환)
adding
추가
push, unshiftconcat, [...arr] spread syntax (example)
removing
삭제
pop, shift, splicefilter, slice (example)
replacing
교체
splice, arr[i] = ... assignmentmap (example)
sorting
정렬
reverse, sortcopy the array first (example)
배열을 복사한 다음 처리

Alternatively, you can use Immer which lets you use methods from both columns. 또는 두 열의 메서드를 모두 사용할 수 있는 Immer를 사용할 수도 있습니다.

Pitfall | 함정

Unfortunately, slice and splice are named similarly but are very different: 안타깝게도 slicesplice는 이름이 비슷하지만 매우 다릅니다:

  • slice lets you copy an array or a part of it.
  • splice mutates the array (to insert or delete items).
  • slice는 배열 또는 배열의 일부를 복사할 수 있습니다.
  • splice는 배열을 항목을 삽입하거나 삭제하기 위해 변이 합니다.

In React, you will be using slice (no p!) a lot more often because you don’t want to mutate objects or arrays in state. Updating Objects explains what mutation is and why it’s not recommended for state. React에서는 state의 객체나 배열을 변이하고 싶지 않기 때문에 slice(p 없음!)를 훨씬 더 자주 사용하게 될 것입니다. 객체 state 업데이트에서 변이가 무엇이고 왜 state에 권장되지 않는지에 대해 설명합니다.

Adding to an array배열에 추가하기

push() will mutate an array, which you don’t want: push()는 배열을 변이합니다 (원하지 않는 방식):

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={() => {
        artists.push({
          id: nextId++,
          name: name,
        });
      }}>Add</button>
      <ul>
        {artists.map(artist => (
          <li key={artist.id}>{artist.name}</li>
        ))}
      </ul>
    </>
  );
}

Instead, create a new array which contains the existing items and a new item at the end. There are multiple ways to do this, but the easiest one is to use the ... array spread syntax: 대신 기존 항목과 끝에 새 항목을 포함하는 새 배열을 만드세요 이 작업을 수행하는 방법은 여러 가지가 있지만 가장 쉬운 방법은 ... 배열 전개 구문을 사용하는 것입니다:

setArtists( // Replace the state
[ // with a new array
...artists, // that contains all the old items
{ id: nextId++, name: name } // and one new item at the end
]
);

Now it works correctly: 이제 올바르게 작동합니다:

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

The array spread syntax also lets you prepend an item by placing it before the original ...artists: 배열 전개 구문을 사용하면 항목을 원본 배열 ...artists 에 배치하여 항목을 추가할 수도 있습니다:

setArtists([
{ id: nextId++, name: name },
...artists // Put old items at the end
]);

In this way, spread can do the job of both push() by adding to the end of an array and unshift() by adding to the beginning of an array. Try it in the sandbox above! 이런 식으로 전개 구문은 배열의 끝에 추가하는 push()와 배열의 시작 부분에 추가하는 unshift()의 기능을 모두 수행할 수 있습니다. 위의 샌드박스에서 사용해 보세요!

Removing from an array배열에서 제거하기

The easiest way to remove an item from an array is to filter it out. In other words, you will produce a new array that will not contain that item. To do this, use the filter method, for example: 배열에서 항목을 제거하는 가장 쉬운 방법은 필터링하는 것입니다. 즉, 해당 항목을 포함하지 않는 새 배열을 생성합니다. 이렇게 하려면 예를 들어, filter 메서드를 사용할 수 있습니다:

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

Click the “Delete” button a few times, and look at its click handler. ‘삭제’ 버튼을 몇 번 클릭한 뒤, 클릭 핸들러를 확인합니다.

setArtists(
artists.filter(a => a.id !== artist.id)
);

Here, artists.filter(a => a.id !== artist.id) means “create an array that consists of those artists whose IDs are different from artist.id”. In other words, each artist’s “Delete” button will filter that artist out of the array, and then request a re-render with the resulting array. Note that filter does not modify the original array. 여기서 artists.filter(a => a.id !== artist.id)구문은 artist.id와 다른 ID를 가진 artists로 구성된 배열을 생성한다”는 의미입니다. 즉, 각 아티스트의 ‘삭제’ 버튼은 배열에서 해당 아티스트를 필터링한 다음 결과 배열로 다시 렌더링하도록 요청합니다. filter는 원래 배열을 수정하지 않는다는 점에 유의하세요.

Transforming an array배열 변경하기

If you want to change some or all items of the array, you can use map() to create a new array. The function you will pass to map can decide what to do with each item, based on its data or its index (or both). 배열의 일부 또는 모든 항목을 변경하려면 map()을 사용하여 새로운 배열을 만들 수 있습니다. map에 전달할 함수는 데이터 또는 인덱스(또는 둘 다)에 따라 각 항목에 대해 수행할 작업을 결정할 수 있습니다.

In this example, an array holds coordinates of two circles and a square. When you press the button, it moves only the circles down by 50 pixels. It does this by producing a new array of data using map(): 이 예제에서 배열은 두 개의 원과 하나의 정사각형 좌표를 포함합니다. 버튼을 누르면 원만 50픽셀 아래로 이동합니다. 이 과은 map()을 사용하여 새로운 데이터 배열을 생성하여 수행됩니다:

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') {
        // No change
        return shape;
      } else {
        // Return a new circle 50px below
        return {
          ...shape,
          y: shape.y + 50,
        };
      }
    });
    // Re-render with the new array
    setShapes(nextShapes);
  }

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

Replacing items in an array배열에서 항목 교체하기

It is particularly common to want to replace one or more items in an array. Assignments like arr[0] = 'bird' are mutating the original array, so instead you’ll want to use map for this as well. 배열에서 하나 이상의 항목을 바꾸고 싶은 경우가 특히 흔합니다. ar[0] = 'bird'와 같은 할당은 원래 배열을 변이하는 것이므로 이 경우에도 map을 사용하는 것이 좋습니다.

To replace an item, create a new array with map. Inside your map call, you will receive the item index as the second argument. Use it to decide whether to return the original item (the first argument) or something else: 항목을 바꾸려면 map으로 새 배열을 만듭니다. map 호출 내에서 두 번째 인수로 항목의 인덱스를 받게 됩니다. 이를 사용하여 원래 항목(첫 번째 인수)을 반환할지 아니면 다른 것을 반환할지 결정할 수 있습니다:

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) {
        // Increment the clicked counter
        return c + 1;
      } else {
        // The rest haven't changed
        return c;
      }
    });
    setCounters(nextCounters);
  }

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

Inserting into an array배열에 삽입하기

Sometimes, you may want to insert an item at a particular position that’s neither at the beginning nor at the end. To do this, you can use the ... array spread syntax together with the slice() method. The slice() method lets you cut a “slice” of the array. To insert an item, you will create an array that spreads the slice before the insertion point, then the new item, and then the rest of the original array. 때로는 시작도 끝도 아닌 특정 위치에 항목을 삽입하고 싶을 때가 있습니다. 이를 위해 ... 배열 전개 구문과 slice() 메서드를 함께 사용할 수 있습니다. slice() 메서드를 사용하면 배열의 “조각”을 잘라낼 수 있습니다. 항목을 삽입하려면 삽입 지점 앞에 slice를 spread한 다음 새 항목과 나머지 원래 배열을 펼치는 배열을 만듭니다.

In this example, the Insert button always inserts at the index 1: 이 예에서 삽입 버튼은 항상 인덱스 1에 삽입됩니다:

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; // Could be any index
    const nextArtists = [
      // Items before the insertion point:
      ...artists.slice(0, insertAt),
      // New item:
      { id: nextId++, name: name },
      // Items after the insertion point:
      ...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>
    </>
  );
}

Making other changes to an array배열에 다른 변경 사항 적용하기

There are some things you can’t do with the spread syntax and non-mutating methods like map() and filter() alone. For example, you may want to reverse or sort an array. The JavaScript reverse() and sort() methods are mutating the original array, so you can’t use them directly. 전개 구문과 map()filter()와 같은 비변이 메서드만으로는 할 수 없는 일이 몇 가지 있습니다. 예를 들어, 배열을 반전시키거나 정렬하고 싶을 수 있습니다. JavaScript reverse()sort() 메서드는 원래 배열을 변이하므로 직접 사용할 수 없습니다.

However, you can copy the array first, and then make changes to it. 대신, 배열을 먼저 복사한 다음 변이하면 됩니다.

For example: 예를 들어:

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

Here, you use the [...list] spread syntax to create a copy of the original array first. Now that you have a copy, you can use mutating methods like nextList.reverse() or nextList.sort(), or even assign individual items with nextList[0] = "something". 여기서는 [...list] 전개 구문을 사용하여 먼저 원본 배열의 복사본을 만듭니다. 이제 복사본이 생겼으므로 nextList.reverse() 또는 nextList.sort()와 같은 변이 메서드를 사용하거나 nextList[0] = "something"으로 개별 항목을 할당할 수도 있습니다.

However, even if you copy an array, you can’t mutate existing items inside of it directly. This is because copying is shallow—the new array will contain the same items as the original one. So if you modify an object inside the copied array, you are mutating the existing state. For example, code like this is a problem. 그러나 배열을 복사하더라도 배열 내부의 기존 항목을 직접 변이할 수는 없습니다. 이는 얕은 복사가 이루어져 새 배열에는 원래 배열과 동일한 항목이 포함되기 때문입니다. 따라서 복사된 배열 내부의 객체를 수정하면 기존 state를 변이하는 것입니다. 예를 들어, 다음과 같은 코드가 문제가 됩니다.

const nextList = [...list];
nextList[0].seen = true; // Problem: mutates list[0]
setList(nextList);

Although nextList and list are two different arrays, nextList[0] and list[0] point to the same object. So by changing nextList[0].seen, you are also changing list[0].seen. This is a state mutation, which you should avoid! You can solve this issue in a similar way to updating nested JavaScript objects—by copying individual items you want to change instead of mutating them. Here’s how. nextListlist는 서로 다른 배열이지만, **nextList[0]****list[0]**은 같은 객체를 가리킵니다. 따라서 nextList[0].seen을 변경하면 list[0].seen도 변경하는 것입니다. 이것은 state를 변이하므로 피해야 합니다! 중첩된 JavaScript 객체 업데이트와 비슷한 방법으로 이 문제를 해결할 수 있는데, 변경하려는 개별 항목을 변이하는 대신 복사하는 것입니다. 방법은 다음과 같습니다.

Updating objects inside arrays배열 내부의 객체 업데이트하기

Objects are not really located “inside” arrays. They might appear to be “inside” in code, but each object in an array is a separate value, to which the array “points”. This is why you need to be careful when changing nested fields like list[0]. Another person’s artwork list may point to the same element of the array! 객체는 실제로 배열 “내부”에 위치하지 않습니다. 코드에서는 “내부”에 있는 것처럼 보일 수 있지만 배열의 각 객체는 배열이 “가리키는” 별도의 값입니다. 그렇기 때문에 list[0]과 같이 중첩된 필드를 변경할 때 주의해야 합니다. 다른 사람의 작품 목록이 배열의 동일한 요소를 가리킬 수 있습니다!

When updating nested state, you need to create copies from the point where you want to update, and all the way up to the top level. Let’s see how this works. 중첩된 state를 업데이트할 때는 업데이트하려는 지점부터 최상위 수준까지 복사본을 만들어야 합니다. 어떻게 작동하는지 살펴보겠습니다.

In this example, two separate artwork lists have the same initial state. They are supposed to be isolated, but because of a mutation, their state is accidentally shared, and checking a box in one list affects the other list: 다음 예에서는 두 개의 개별 작품 목록의 초기 state가 동일합니다. 두 목록은 분리되어야 하지만 변이로 인해 state가 실수로 공유되어 한 목록의 체크박스를 선택하면 다른 목록에 영향을 미칩니다:

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

The problem is in code like this: 문제는 다음 코드에 있습니다:

const myNextList = [...myList];
const artwork = myNextList.find(a => a.id === artworkId);
artwork.seen = nextSeen; // Problem: mutates an existing item
setMyList(myNextList);

Although the myNextList array itself is new, the items themselves are the same as in the original myList array. So changing artwork.seen changes the original artwork item. That artwork item is also in yourList, which causes the bug. Bugs like this can be difficult to think about, but thankfully they disappear if you avoid mutating state. myNextList 배열 자체는 새 배열이지만, 항목 자체는 원래의myList 배열과 동일합니다. 따라서artwork.seen을 변경하면 원본 작품 항목이 변경됩니다. 해당 작품 항목도 yourArtworks에 있으므로 버그가 발생합니다. 이와 같은 버그는 생각하기 어려울 수 있지만 다행히도 state를 변이하지 않으면 사라집니다.

You can use map to substitute an old item with its updated version without mutation. map을 사용하여 이전 항목에 대한 변이 없이 업데이트된 버전으로 대체할 수 있습니다.

setMyList(myList.map(artwork => {
if (artwork.id === artworkId) {
// Create a *new* object with changes
return { ...artwork, seen: nextSeen };
} else {
// No changes
return artwork;
}
}));

Here, ... is the object spread syntax used to create a copy of an object. 여기서 ...객체의 복사본을 만드는 데 사용되는 객체 전개 구문입니다.

With this approach, none of the existing state items are being mutated, and the bug is fixed: 이 접근 방식을 사용하면 기존 state 항목이 변이되지 않으며 버그가 수정됩니다:

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) {
        // Create a *new* object with changes
        return { ...artwork, seen: nextSeen };
      } else {
        // No changes
        return artwork;
      }
    }));
  }

  function handleToggleYourList(artworkId, nextSeen) {
    setYourList(yourList.map(artwork => {
      if (artwork.id === artworkId) {
        // Create a *new* object with changes
        return { ...artwork, seen: nextSeen };
      } else {
        // No changes
        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>
  );
}

In general, you should only mutate objects that you have just created. If you were inserting a new artwork, you could mutate it, but if you’re dealing with something that’s already in state, you need to make a copy. 일반적으로 방금 만든 객체만 변이해야 합니다. 새로운 artwork을 삽입하는 경우에는 변이해도 되지만, 이미 있는 state의 artwork을 다루는 경우에는 복사본을 만들어야 할 겁니다.

Write concise update logic with ImmerImmer로 간결한 업데이트 로직 작성하기

Updating nested arrays without mutation can get a little bit repetitive. Just as with objects: 중첩 배열을 변이 없이 업데이트하는 작업은 객체를 다룰 때와 같이 약간 반복적일 수 있습니다:

  • Generally, you shouldn’t need to update state more than a couple of levels deep. If your state objects are very deep, you might want to restructure them differently so that they are flat.
  • If you don’t want to change your state structure, you might prefer to use Immer, which lets you write using the convenient but mutating syntax and takes care of producing the copies for you.
  • 일반적으로 state를 몇 레벨 이상 깊이 업데이트할 필요는 없습니다. state 객체가 매우 깊다면 다르게 재구성하여 평평하게 만드는 것이 좋습니다.
  • state 구조를 변경하고 싶지 않다면, Immer를 사용해보세요. Immer는 변이 구문을 사용하여 작성하더라도 자동으로 사본 생성을 처리해 주어 편리합니다.

Here is the Art Bucket List example rewritten with Immer: 다음은 Immer로 재작성한 아트 버킷 리스트 예시입니다:

{
  "dependencies": {
    "immer": "1.7.3",
    "react": "latest",
    "react-dom": "latest",
    "react-scripts": "latest",
    "use-immer": "0.5.1"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  },
  "devDependencies": {}
}

Note how with Immer, mutation like artwork.seen = nextSeen is now okay: Immer를 사용하면 이제 artwork.seen = nextSeen과 같은 변이도 괜찮습니다:

updateMyTodos(draft => {
const artwork = draft.find(a => a.id === artworkId);
artwork.seen = nextSeen;
});

This is because you’re not mutating the original state, but you’re mutating a special draft object provided by Immer. Similarly, you can apply mutating methods like push() and pop() to the content of the draft. 이는 원본 state를 변이하는 것이 아니라 Immer가 제공하는 특별한 draft객체를 변이하기 때문입니다. 마찬가지로 push()pop()과 같은 변이 메서드를 draft의 콘텐츠에 적용할 수 있습니다.

Behind the scenes, Immer always constructs the next state from scratch according to the changes that you’ve done to the draft. This keeps your event handlers very concise without ever mutating state. 백그라운드에서 Immer는 항상 사용자가 draft에 적용한 변경 사항에 따라 다음 state를 처음부터 다시 구성합니다. 이렇게 하면 이벤트 핸들러가 state를 변이하지 않고도 매우 간결하게 유지됩니다.

Recap요약

  • You can put arrays into state, but you can’t change them.
  • Instead of mutating an array, create a new version of it, and update the state to it.
  • You can use the [...arr, newItem] array spread syntax to create arrays with new items.
  • You can use filter() and map() to create new arrays with filtered or transformed items.
  • You can use Immer to keep your code concise.
  • 배열을 state에 넣을 수는 있지만 변경할 수는 없습니다.
  • 배열을 변이하는 대신 배열의 새로운 버전을 생성하고 state를 업데이트하세요.
  • 배열 전개 구문 [...arr, newItem]을 사용하여 새 항목으로 배열을 만들 수 있습니다.
  • filter()map()을 사용하여 필터링되거나 변형된 항목으로 새 배열을 만들 수 있습니다.
  • Immer를 사용하면 코드를 간결하게 유지할 수 있습니다.

Challenge 1 of 4: Update an item in the shopping cart장바구니 품목 업데이트하기

Fill in the handleIncreaseClick logic so that pressing ”+” increases the corresponding number: ”+“를 누르면 해당 숫자가 증가하도록 handleIncreaseClick 로직을 채우세요:

import { useState } from 'react';

const initialProducts = [{
  id: 0,
  name: 'Baklava',
  count: 1,
}, {
  id: 1,
  name: 'Cheese',
  count: 5,
}, {
  id: 2,
  name: 'Spaghetti',
  count: 2,
}];

export default function ShoppingCart() {
  const [
    products,
    setProducts
  ] = useState(initialProducts)

  function handleIncreaseClick(productId) {

  }

  return (
    <ul>
      {products.map(product => (
        <li key={product.id}>
          {product.name}
          {' '}
          (<b>{product.count}</b>)
          <button onClick={() => {
            handleIncreaseClick(product.id);
          }}>
            +
          </button>
        </li>
      ))}
    </ul>
  );
}