[React] 공식문서 톺아보기 3

Part 3. 객체 State의 불변성

React에서 State로 객체를 다룰 때는 불변성을 지켜야 한다는 규칙이 있다. 기존 객체를 직접 수정하는 대신, 새로운 객체를 생성하여 교체해야 한다. 이 과정에서 자바스크립트의 전개 구문을 사용하게 되는데, 객체의 참조 특성 때문에 논리적 오류가 빈번하게 발생한다고 한다.

1. 전개 구문과 불변성

질문: setPerson({ …person, firstName: e.target.value })라는 문법이 직관적이지 않다. JSX인지 자바스크립트인지 혼동된다.

React 코드 내에서 중괄호가 사용되면 JSX의 일부인지 자바스크립트 객체 리터럴인지 헷갈릴 수 있다.

답변: 기존 내용을 펼쳐 놓고, 새로운 값으로 덮어쓴다.

이 코드는 순수한 자바스크립트 영역이다. setPerson 함수 내부의 중괄호는 새로운 객체를 생성하겠다는 의미다. 여기서 …person은 기존 person 객체에 담긴 모든 속성을 새 객체에 낱개로 풀어서 복사하라는 명령이다.

그 뒤에 작성된 firstName 속성은 앞서 복사된 내용 중 같은 이름을 가진 속성을 덮어쓴다. 기존 데이터를 모두 가져오되 firstName만 새로운 값으로 교체한 새 객체를 만드는 것이다. 이 문법은 불변성을 유지하면서 특정 필드만 업데이트하는 React의 표준 패턴이다.

2. 얕은 복사의 한계

질문: 전개 구문은 얕다고 한다. 이것이 데이터 관리에 어떤 영향을 미치는가?

문서에서는 전개 구문이 한 레벨 깊이의 내용만 복사한다고 설명한다. 익숙한 주제이기도 하지만 리액트에서는 이것이 실제 코드에서 어떤 문제를 일으키는지, 왜 깊은 곳의 데이터를 수정할 때 주의해야 할까?

답변: 겉만 새로 만들고 속은 공유한다.

얕은 복사란 객체의 가장 바깥쪽 껍데기만 새로 만들고, 그 안에 들어있는 또 다른 객체나 배열은 메모리 주소만 복사하는 것을 의미한다. 만약 person 객체 안에 specs라는 내부 객체가 있다면, …person을 실행해도 specs 객체는 새로 만들어지지 않고 원본과 같은 객체를 가리킨다.

따라서 복사본의 specs를 수정하면 원본의 specs도 함께 변경되는 부작용이 발생한다. 이를 방지하려면 내부 객체까지 접근하여 전개 구문을 한 번 더 사용해야 한다. 중첩된 데이터 구조가 깊어질수록 코드가 복잡해지는 원인이 된다.

3. 객체 업데이트 시 발생하는 논리적 오류

질문: score: score + 1이라고 작성했는데 에러가 발생하거나 값이 사라진다.

새로운 객체를 만들 때 score 값을 1 증가시키려 했다. 하지만 score가 정의되지 않았다는 에러가 발생하거나, 실수로 …player를 빠뜨렸을 때 데이터가 증발하는 현상을 겪는다.

답변: 객체 내부와 외부의 스코프 차이

객체 리터럴 안에서 score라고만 작성하면 자바스크립트는 이를 변수로 인식하고 찾는다. 하지만 score는 player 객체 안에 들어있는 속성일 뿐, 별도로 선언된 변수가 아니므로 에러가 발생한다. 반드시 player.score로 접근하거나 구조 분해 할당을 통해 미리 변수로 추출해야 한다.

또한 …player를 누락하면 기존 데이터가 복사되지 않아, 수정하려는 속성 외의 모든 데이터가 사라진다.

데이터가 사라졌는데 왜 화면에는 남아있는가?

실수로 …player를 누락하여 firstName 속성이 사라진 상황을 가정해 보자. 논리적으로는 firstName이 undefined가 되었으므로 화면의 입력창도 비워져야 한다. 하지만 실제로는 입력창에 글자가 그대로 남아있는 현상이 발생한다. 반면 단순히 텍스트로 렌더링 된 score는 즉시 사라진다.

이 현상의 원인은 React의 제어 컴포넌트 동작 방식에 있었다. input 태그의 value에 undefined가 전달되면, React는 더 이상 이 입력을 제어하지 않겠다는 신호로 받아들인다. 말은 즉슨, 비제어 컴포넌트로 전환된다.

이때부터 input 태그는 React의 state와 연결이 끊긴 채, 브라우저가 관리하는 DOM에 남아있는 마지막 텍스트를 그대로 보여준다. 데이터는 이미 사라졌지만 화면 껍데기만 남아있는 착시 현상이다. 반면 일반 텍스트 노드는 undefined를 공백으로 처리하므로 즉시 사라진다. 데이터가 논리적으로 삭제되었다면 화면에서도 사라져야 한다는 직관이 맞았으며, 예외적인 동작은 React가 에러를 방지하려는 처리 과정에서 발생한 부작용이었다.


© 2022. All rights reserved.

Powered by Hydejack v9.2.1