[React] 공식문서 톺아보기 4
Part 4. 배열 State를 조작하는 방법
자바스크립트의 배열은 push나 pop 같은 메서드로 내용을 직접 변경하는 것이 가능하다. 하지만 React의 State는 읽기 전용으로 취급해야 하므로 이러한 변경 메서드를 사용할 수 없다. 대신 기존 배열을 복사하여 새로운 배열을 만드는 전개 구문(...)과 map, filter 함수를 사용해야 한다. 이 과정에서 순서와 문법적 차이가 결과에 큰 영향을 미친다.
1. 배열의 추가와 순서
[질문: 순서 변경만으로 앞뒤 배치가 바뀌는 이유는?] 배열에 새로운 항목을 추가할 때, 전개 구문의 위치에 따라 항목이 추가되는 위치가 달라진다. [...artists, newItem]은 뒤에 추가되고, [newItem, ...artists]는 앞에 추가된다. 별도의 함수(push, unshift) 없이 단순히 작성 순서만 바꿨는데 동작이 달라지는 원리가 무엇일까?
[답변: 코드가 작성된 순서 그대로 새 배열에 담긴다] 자바스크립트가 새로운 배열([])을 생성하는 방식이 매우 직관적이기 때문이다. 컴퓨터는 대괄호 안의 내용을 왼쪽에서 오른쪽으로 순서대로 읽으며 새 배열을 채운다.
// 1. newItem을 먼저 넣고, 그 뒤에 기존 artists를 쏟아붓는다.
[newItem, ...artists]
// 2. 기존 artists를 먼저 쏟아붓고, 그 뒤에 newItem을 넣는다.
[...artists, newItem]
...artists는 기존 배열의 포장을 뜯어 내용물을 나열하는 것이다. 따라서 newItem을 먼저 적으면 새 배열의 0번 인덱스에 자리 잡고, 나중에 적으면 기존 내용물들의 뒤인 마지막 인덱스에 자리 잡는다.
2. 배열의 수정 (Map)
[질문: 하나만 바꾸고 싶은데 왜 전체를 순회하는 map을 쓰는가?] 배열의 특정 인덱스에 있는 값을 수정하고 싶을 때, 보통 arr[0] = 'newValue'처럼 콕 집어서 바꾸는 방식을 떠올린다. 하지만 React에서는 원본을 건드리지 않기 위해 map을 사용하여 배열 전체를 새로 만든다. 처음엔 비효율적으로 보이는 이 방식이 정석인 이유가 이해되지 않았다.
[답변: 조건부 복사를 수행하자] map은 배열의 모든 요소를 하나씩 꺼내 검사하고, 그 결과를 모아 길이가 같은 새 배열을 만든다. React에서 map은 조건부 교체기로 사용된다고 이해했다.
counters.map((c, i) => {
if (i === index) {
// 클릭된 것만 값을 바꾼다 (+1)
return c + 1;
} else {
// 나머지는 그대로 반환한다 (유지)
return c;
}
});
map 함수 내부의 if-else 문은 모든 요소에게 “네가 수정할 대상이니?”라고 묻는다. 대상이라면 새로운 값을 반환하고, 아니라면 기존 값을 그대로 반환한다. 결과적으로 수정 대상만 값이 바뀌고 나머지는 그대로 복사된 새로운 배열이 탄생한다. 이것이 불변성을 지키면서 특정 항목을 수정하는 표준 패턴이다.
3. 배열의 삭제 (Filter)와 중괄호 주의점
[질문: 코드를 짰는데 삭제가 안 되거나 몽땅 사라진다.] 배열에서 특정 항목을 지우기 위해 filter를 사용했다. 그런데 삭제 버튼을 눌렀더니 아무런 반응이 없거나, 오히려 배열이 텅 비어버리는 현상이 발생한다.
[답변: 화살표 함수의 반환 규칙 위반] filter는 조건이 true인 항목만 남겨서 새 배열을 만든다. 문제는 자바스크립트 화살표 함수(=>)의 문법적 특성에서 발생한다.
// 잘못된 예: 중괄호 {}를 썼는데 return이 없다.
products.filter(p => {
p.id !== productId // 반환값 없음(undefined) -> false 취급 -> 모두 삭제됨
})
// 올바른 예 1: return 명시
products.filter(p => {
return p.id !== productId;
})
// 올바른 예 2: 중괄호 생략 (암시적 반환)
products.filter(p => p.id !== productId);
화살표 함수 뒤에 중괄호 {}를 열었다면, 반드시 return 키워드를 적어줘야 값을 밖으로 내보낸다. return을 생략하면 undefined를 반환하는데, filter는 이를 false로 간주하여 모든 항목을 배열에서 제외해 버린다.