v19.2Latest

State에서 배열 업데이트하기

JavaScript에서 배열은 가변적이지만, state에 저장할 때는 불변으로 취급해야 합니다. 객체와 마찬가지로, state에 저장된 배열을 업데이트하려면 새로운 배열을 생성하거나(또는 기존 배열의 복사본을 만들고), 그 새로운 배열을 사용하도록 state를 설정해야 합니다.

배울 내용
  • React state에서 배열의 항목을 추가, 제거 또는 변경하는 방법
  • 배열 내부의 객체를 업데이트하는 방법
  • Immer를 사용하여 배열 복사를 덜 반복적으로 만드는 방법

변이 없이 배열 업데이트하기

JavaScript에서 배열은 또 다른 종류의 객체입니다.객체와 마찬가지로,React state의 배열은 읽기 전용으로 취급해야 합니다. 즉, arr[0] = 'bird'처럼 배열 내부의 항목을 재할당해서는 안 되며,push()pop()와 같이 배열을 변이시키는 메서드도 사용해서는 안 됩니다.

대신, 배열을 업데이트할 때마다 state 설정 함수에새로운 배열을 전달해야 합니다. 이를 위해 state의 원본 배열에서 filter()map()와 같은 비변이 메서드를 호출하여 새로운 배열을 생성할 수 있습니다. 그런 다음 state를 결과로 나온 새로운 배열로 설정하면 됩니다.

다음은 일반적인 배열 연산에 대한 참조 표입니다. React state 내부의 배열을 다룰 때는 왼쪽 열의 메서드를 피하고 오른쪽 열의 메서드를 선호해야 합니다:

피해야 할 것 (배열을 변이시킴)선호할 것 (새 배열을 반환함)
추가하기push,unshiftconcat,[...arr]전개 구문 (예시)
제거하기pop,shift,splicefilter,slice(예시)
교체하기splice,arr[i] = ... 할당map(예시)
정렬하기reverse,sort먼저 배열을 복사하기 (예시)

또는 Immer를 사용하여 두 열의 메서드를 모두 사용할 수 있습니다.

주의사항

안타깝게도,slicesplice는 이름이 비슷하지만 매우 다릅니다:

  • slice는 배열 또는 그 일부를 복사할 수 있게 합니다.
  • splice배열을 변이시킵니다(항목을 삽입하거나 삭제하기 위해).

React에서는 state의 객체나 배열을 변이시키지 않으려 하기 때문에slice(p가 없습니다!)를 훨씬 더 자주 사용하게 될 것입니다.객체 업데이트하기에서 변이가 무엇이며 state에 권장되지 않는 이유를 설명합니다.

배열에 추가하기

push()는 배열을 변이시키며, 이는 원하지 않는 동작입니다:

대신, 기존 항목들을 포함하고 끝에 새 항목이 있는새로운배열을 생성하세요. 이를 수행하는 방법은 여러 가지이지만, 가장 쉬운 방법은...배열 전개구문을 사용하는 것입니다:

이제 올바르게 작동합니다:

배열 전개 구문을 사용하면 항목을 원본앞에 ...artists배치하여 앞에 추가할 수도 있습니다:

이런 식으로 전개 구문은 배열 끝에 추가하는push()와 배열 앞에 추가하는unshift()의 역할을 모두 수행할 수 있습니다. 위의 샌드박스에서 시도해 보세요!

배열에서 항목 제거하기

배열에서 항목을 제거하는 가장 쉬운 방법은 해당 항목을필터링하여 제외하는 것입니다. 즉, 해당 항목을 포함하지 않는 새로운 배열을 생성하는 것입니다. 이를 위해filter메서드를 사용하세요. 예를 들어:

"Delete" 버튼을 몇 번 클릭하고 클릭 핸들러를 살펴보세요.

여기서artists.filter(a => a.id !== artist.id)는 "ID가 artists와 다른 artist.id로 구성된 배열을 생성하라"는 의미입니다. 즉, 각 아티스트의 "Delete" 버튼은 배열에서해당아티스트를 필터링하여 제거한 다음, 결과 배열로 다시 렌더링을 요청합니다.filter는 원본 배열을 수정하지 않습니다.

배열 변환하기

배열의 일부 또는 모든 항목을 변경하려면map()을 사용하여새로운배열을 생성할 수 있습니다.map에 전달할 함수는 각 항목의 데이터나 인덱스(또는 둘 다)를 기반으로 해당 항목을 어떻게 처리할지 결정할 수 있습니다.

이 예제에서 배열은 두 개의 원과 하나의 사각형의 좌표를 담고 있습니다. 버튼을 누르면 원만 50픽셀 아래로 이동시킵니다. 이는map()을 사용하여 새로운 데이터 배열을 생성함으로써 수행됩니다:

배열에서 항목 교체하기

배열에서 하나 이상의 항목을 교체하는 것은 특히 흔한 작업입니다.arr[0] = 'bird'와 같은 할당은 원본 배열을 변이시키므로, 대신 이 경우에도map을 사용해야 합니다.

항목을 교체하려면 map으로 새로운 배열을 생성하세요.map호출 내부에서는 두 번째 인수로 항목 인덱스를 받습니다. 이를 사용하여 원본 항목(첫 번째 인수)을 반환할지 다른 것을 반환할지 결정하세요:

배열에 항목 삽입하기

때로는 항목을 처음이나 끝이 아닌 특정 위치에 삽입하고 싶을 수 있습니다. 이를 위해... 배열 전개 구문과 slice()메서드를 함께 사용할 수 있습니다.slice()메서드를 사용하면 배열의 "일부"를 잘라낼 수 있습니다. 항목을 삽입하려면 삽입 지점이전의 슬라이스를 전개한 배열, 새 항목, 그리고 원본 배열의 나머지 부분을 포함하는 배열을 생성하면 됩니다.

이 예시에서 Insert 버튼은 항상 인덱스1에 삽입합니다:

배열에 다른 변경 사항 적용하기

전개 구문과 map(), filter()같은 비변이 메서드만으로는 할 수 없는 작업들이 있습니다. 예를 들어, 배열을 뒤집거나 정렬하고 싶을 수 있습니다. JavaScript의reverse()sort()메서드는 원본 배열을 변이시키므로 직접 사용할 수 없습니다.

하지만 먼저 배열을 복사한 다음 변경 사항을 적용할 수 있습니다.

예를 들어:

여기서는 [...list]전개 구문을 사용해 먼저 원본 배열의 복사본을 만듭니다. 이제 복사본이 있으므로nextList.reverse()nextList.sort()같은 변이 메서드를 사용하거나,nextList[0] = "something"로 개별 항목을 할당할 수도 있습니다.

하지만,배열을 복사하더라도 그 안에 있는 기존 항목을 직접 변이시킬 수는 없습니다. 이는 복사가 얕기 때문입니다—새 배열은 원본과 동일한 항목들을 포함합니다. 따라서 복사된 배열 내부의 객체를 수정하면 기존 상태를 변이시키게 됩니다. 예를 들어, 다음과 같은 코드는 문제가 됩니다.

비록 nextListlist가 서로 다른 두 배열이지만,nextList[0]list[0]은 동일한 객체를 가리킵니다. 따라서 nextList[0].seen을 변경하면 list[0].seen도 변경됩니다. 이는 상태 변이로, 피해야 합니다! 이 문제는중첩된 JavaScript 객체 업데이트하기와 유사한 방식으로 해결할 수 있습니다—객체를 변이시키는 대신 변경하려는 개별 항목을 복사하는 방식입니다. 방법은 다음과 같습니다.

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

객체는 배열 "안에"실제로위치하지 않습니다. 코드상에서는 "안에" 있는 것처럼 보일 수 있지만, 배열의 각 객체는 배열이 "가리키는" 별도의 값입니다. 이것이 바로list[0]과 같은 중첩된 필드를 변경할 때 주의해야 하는 이유입니다. 다른 사람의 아트워크 목록이 동일한 배열 요소를 가리킬 수도 있습니다!

중첩된 상태를 업데이트할 때는 업데이트하려는 지점부터 최상위 레벨까지 모두 복사본을 만들어야 합니다.이 방법이 어떻게 작동하는지 살펴보겠습니다.

이 예시에서 두 개의 별도 아트워크 목록이 동일한 초기 상태를 가지고 있습니다. 이들은 서로 격리되어 있어야 하지만, 변이로 인해 상태가 실수로 공유되어 한 목록에서 체크박스를 선택하면 다른 목록에도 영향을 미칩니다:

문제는 다음과 같은 코드에 있습니다:

비록 myNextList배열 자체는 새 것이지만,항목 자체는 원래 myList배열에 있던 것과 동일합니다. 따라서artwork.seen을 변경하면원본artwork 항목이 변경됩니다. 그 artwork 항목은yourList에도 존재하므로 버그가 발생합니다. 이러한 버그는 생각하기 어려울 수 있지만, 다행히도 상태를 변이하지 않으면 사라집니다.

변이 없이 오래된 항목을 업데이트된 버전으로 대체하려면map을 사용할 수 있습니다.

여기서...는 객체를 복사하기 위해 사용되는 객체 전개 구문입니다.객체를 복사하는 방법에 대해 알아보세요.

이 접근 방식으로는 기존 상태 항목 중 어느 것도 변이되지 않으며, 버그가 수정됩니다:

일반적으로,방금 생성한 객체만 변이해야 합니다.새로운 artwork을 삽입하는 경우에는 변이할 수 있지만, 이미 상태에 있는 것을 다룰 때는 복사본을 만들어야 합니다.

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

변이 없이 중첩된 배열을 업데이트하는 것은 약간 반복적일 수 있습니다.객체의 경우와 마찬가지로:

  • 일반적으로 상태를 두세 단계 이상 깊이 업데이트할 필요는 없습니다. 상태 객체가 매우 깊다면,다르게 재구성하여평평하게 만드는 것이 좋습니다.
  • 상태 구조를 변경하고 싶지 않다면, 편리하지만 변이하는 구문을 사용하여 작성하고 복사본 생성을 처리해 주는Immer를 선호할 수 있습니다.

다음은 Immer로 다시 작성한 Art Bucket List 예제입니다:

Immer를 사용하면다음과 같은 변형이artwork.seen = nextSeen이제 괜찮다는 점에 주목하세요:

이는 원본상태를 변형하는 것이 아니라, Immer가 제공하는 특별한draft 객체를 변형하기 때문입니다. 마찬가지로 push()pop()같은 변형 메서드를draft의 내용에 적용할 수 있습니다.

내부적으로 Immer는 항상 draft에 가한 변경 사항에 따라 다음 상태를 처음부터 구성합니다. 이렇게 하면 상태를 절대 변형하지 않으면서도 이벤트 핸들러를 매우 간결하게 유지할 수 있습니다.

요약

  • 배열을 상태에 넣을 수 있지만, 변경할 수는 없습니다.
  • 배열을 변형하는 대신, 배열의새로운버전을 만들고 상태를 그것으로 업데이트하세요.
  • 새 항목이 있는 배열을 만들기 위해[...arr, newItem]배열 전개 구문을 사용할 수 있습니다.
  • 필터링되거나 변환된 항목이 있는 새 배열을 만들기 위해filter()map()을 사용할 수 있습니다.
  • 코드를 간결하게 유지하려면 Immer를 사용하세요.

Try out some challenges

Challenge 1 of 4:Update an item in the shopping cart #

Fill in the handleIncreaseClick logic so that pressing ”+” increases the corresponding number: