[React][공식문서] state 끌어올리기

Gyuwon Lee·2022년 7월 4일
0
post-thumbnail

React 공식 튜토리얼을 바탕으로, 필요한 개념을 보충하여 학습한 기록입니다.

1. state 는 어떤 컴포넌트가 가져야 할까?

리액트로 프로그램을 만들다 보면 이 정보를 어떤 컴포넌트가 쥐고 있어야 하는지 고민하게 될 때가 잦다. 특히 한 종류의 정보를 여러 엘리먼트들이 참조하거나 수정해야 하는 경우 이 고민은 더 심해진다.

예를 들어, 간단히 <input /> 태그를 바탕으로 메모장 기능을 만드는 경우를 생각해 보자. content 라고 이름붙인 문자열 변수를 사용할 것이다.

이 경우 content 의 값이 들어가야 하는 공간은 두 곳이다:

  • 기존 혹은 업데이트된 메모장 내용이 보여지는 텍스트 필드
  • 업데이트하고자 하는 메모장 내용을 적을 입력 필드

이 두 필드가 아래와 같이 동일한 부모 요소 아래 같은 계층에 놓여 있다고 생각해 보자.

<MemoContainer>
  <MemoText />
  <MemoInput />
</MemoContainer>

이 경우, MemoInput 에서 content 의 값을 변경시킨다면 MemoText 에도 즉시 반영되어야 한다. 하지만 리액트는 단방향 데이터 흐름 내지는 하향식 데이터 흐름을 지향하는 라이브러리로, 같은 계층의 컴포넌트끼리는 데이터를 주고받기 어렵다.

공식 문서의 권장사항

보통의 경우, state는 렌더링 시 그 값을 당장 필요로 하는 컴포넌트에 먼저 추가된다. 그러고 나서 다른 컴포넌트도 역시 그 값이 필요하게 되면 그 값을 그들의 가장 가까운 공통 조상으로 끌어올리면 된다.

위 예시의 경우에는 우선 당장 값을 저장하기 위해 MemoText 컴포넌트에 content state를 둔다. 이후 MemoInput 컴포넌트가 만들어지면서 마찬가지로 content state를 필요로 하게 되므로, 이 state를 이동시킬 필요가 발생한다. 이 때 두 컴포넌트의 공통 조상인 MemoContainer 가 이 값을 state로 갖게 된다.

왜 그래야 하나요?

리액트 애플리케이션 안에서 변경이 일어나는 데이터에 대해서는 “진리의 원천(source of truth)“을 하나만 두어야 한다. 이는 데이터의 변경 내역 및 버그 지점을 추적하기 용이하게 만든다는 장점이 있다.

직접 개인 프로젝트를 시작해 보니 프론트엔드는 특성상 데이터를 '들고 다닐' 일이 아주 빈번히 일어난다는 것을 느꼈다. 페이지에서 페이지로 이동할 때, 혹은 부모 라우트에서 자식 라우트를 navigate 할 때, 하다못해 버튼을 눌러 모달 하나를 띄울 때에도 계속 데이터의 전달이 이루어져야 한다.

사이트의 구조가 복잡해질수록 이러한 데이터의 교환은 더 빈번하게, 촘촘하게 발생하므로 데이터의 변경 및 전달 구조를 엄격하게 관리하지 않으면 도미노가 무너지듯 데이터의 오염이 걷잡기 어려운 수준으로 퍼져나갈 수도 있음을 체감했다.

리액트 역시 그러한 이유로 불변성을 지키고자 데이터가 무조건 위에서 아래로만 흐를 수 있도록 위와 같은 방식을 제안하고 있는 것이 아닐까 싶었다.


2. bottom-up 으로 데이터를 변경하고 싶다면?

하지만 코드를 짜다 보면 원칙을 지키기란 쉽지 않다. 무조건 부모 컴포넌트가 state의 값을 저장 및 변경하도록 제어하는 것은 사실 불가능하다. 또한 객체지향의 단일 책임 원칙까지 생각한다면, 부모 컴포넌트가 여러 값에 대한 저장 및 변경 권한을 모두 갖고 있는 것은 한 컴포넌트가 너무 많은 책임을 지는 경우에 해당한다.

위 예제에서도, 값을 갖고 있는 건 부모 컴포넌트인 MemoContainer 가 될 수 있지만 값을 변경하는 기능까지도 부모 컴포넌트가 가질 필요는 없어 보인다. MemoContainer 는 이름 그대로 자식 컴포넌트들을 '담고' 있는 용도이지, 값을 수정하는 용도가 아니기 때문이다. 값의 수정은 그 자식인 MemoInput 안에서만 발생할 것이다.

따라서, 리액트는 다음과 같은 방식으로 상위 컴포넌트의 state를 변경하도록 허용하고 있다:

  • 우선 공통 조상(상위 컴포넌트)가 state , setState 를 갖는다.
    • 자식 컴포넌트는 오직 props 를 통해서만 값을 전달받는다. 이 때 props 는 read-only다.
    • props로 넘겨받은 부모 컴포넌트의 state를 변경하고 싶은 경우, props로 state와 같이 setState 함수 를 전달받으면 된다.
  • 다른 props나 state로부터 계산될 수 있는 값은 state로 두지 않는다.

3. TL;DR: 작게 생각하자

프로그램을 만들며 초반부터 "이 값은 나중에 A, B, C 컴포넌트들이 공유해야 하니까 일단 props로 받게 두고 진행하자" 라는 생각을 하기란 어렵다. 초반 개발 속도가 느려진다는 점에서도 그렇지만, 무조건 상위에 있는 컴포넌트가 state를 갖도록 구조를 짜다 보면 컴포넌트의 책임이 온전히 캡슐화되지 못할 수도 있기 때문에 위험하다.

그러니 우선은 당장 값을 필요로 하는 컴포넌트가 state를 가질 수 있도록 하고, 프로그램이 복잡해지면서 해당 값을 다른 컴포넌트와 공유 및 동기화해야 할 일이 생기면 그 때 공통 조상이 되는 컴포넌트를 찾아 state를 올려 주는 작업을 해 주는 편이 낫다.


참고:

profile
하루가 모여 역사가 된다

0개의 댓글