Menu

리액트 상태 끌어올리기: 컴포넌트 간 state 공유

상태 끌어올리기는 두 컴포넌트의 state를 가장 가까운 공통 부모로 옮기고, 부모가 값과 setter를 props로 다시 내려 주는 것입니다. 언제 끌어올릴지, 세 단계로 끌어올리는 방법, 그리고 state를 지역에 남겨야 하는 경우를 배웁니다.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

상태 끌어올리기는 state를 공유해야 하는 컴포넌트들에서 그것들의 가장 가까운 공통 부모로 옮기는 것입니다. 부모가 값을 보관하고, prop으로 내려 주고, 자식이 값을 바꿀 때 호출하는 함수도 넘기므로, 모든 자식이 하나의 state 사본으로 렌더링합니다.

아무 패널에서나 "Show"를 클릭하세요. 그 패널이 열리고, 열려 있던 패널은 닫힙니다. 패널들이 스스로 결정하지 않기 때문에 한 번에 하나만 열 수 있습니다. App이 openIndex를 보관하고, 각 Panel은 isOpen과 onOpen 함수만 받습니다.

문제: 서로 일치해야 하는 state

모든 패널이 자기만의 isOpen state를 가지는 뻔한 버전부터 시작해 봅시다. 각 패널은 잘 동작하지만, 패널들은 서로에 대해 아무것도 모르므로 두 패널이 동시에 열리는 것을 막을 방법이 없습니다.

두 패널을 모두 열어 보세요. 둘 다 열린 채로 있습니다. React의 state는 그것을 선언한 컴포넌트에만 속하므로 형제가 읽거나 초기화할 수 없습니다. 두 컴포넌트가 서로 일치해야 한다면 state는 두 컴포넌트보다 위에 있어야 합니다.

세 단계로 상태 끌어올리기

두 번째 예제를 첫 번째 예제로 바꾸려면 세 가지를 수정합니다.

  1. 자식에서 state를 제거합니다. Panel의 useState를 지우고 대신 props에서 isOpen을 읽습니다. 자식은 더 이상 자기가 열려 있는지 결정하지 않습니다.
  2. 부모에서 값과 그 값을 바꾸는 방법을 넘깁니다. Panel은 isOpen과 onOpen 콜백을 받습니다. 자식은 버튼이 클릭되면 onOpen()을 호출할 뿐, 부모가 그것으로 무엇을 하는지는 모릅니다.
  3. 공통 부모에 state를 추가합니다. App이 openIndex를 선언하고 이를 각 패널의 props로 바꿉니다: isOpen={openIndex === 1}, onOpen={() => setOpenIndex(1)}.

가장 가까운 공통 부모는 state가 필요한 모든 컴포넌트를 렌더링하는 가장 낮은 컴포넌트입니다. 여기서는 App입니다. 패널들이 Faq 컴포넌트 안에 있다면 state는 그보다 높은 곳이 아니라 Faq에 들어가야 합니다.

끌어올린 뒤의 Panel은 제어 input과 같은 의미로 부모에게 제어됩니다. props가 말하는 것을 보여 주고, 변경은 콜백으로 알립니다. 제어 컴포넌트와 비제어 컴포넌트 페이지에서 폼 요소에 대한 같은 개념을 다룹니다.

단일 진실의 원천

화면의 두 부분이 같은 사실을 보여 준다면, 그 사실은 한 번만 저장하고 나머지는 모두 그것에서 계산하세요. 온도 변환기가 대표적인 예입니다. 섭씨와 화씨 입력은 항상 일치해야 하므로 각자 자기 숫자를 가질 수 없습니다.

어느 필드에 입력하든 다른 필드가 따라옵니다. state는 하나의 사실, 즉 사용자가 마지막으로 입력한 숫자와 그 단위입니다. 다른 필드는 렌더링 중에 그것으로 계산되므로, 입력 중인 필드는 항상 입력한 그대로를 유지합니다. 시작 state를 { value: '212', scale: 'f' }로 바꾸면 입력창 아래의 메시지가 "Water boils."로 바뀝니다.

섭씨 숫자와 화씨 숫자를 두 개의 state에 보관하면 모든 핸들러가 둘 다 업데이트해야 하고, 한 핸들러가 잊는 순간 두 입력이 어긋납니다. 저장된 값이 하나라면 스스로 어긋날 수 없습니다.

setter 내려 주기

자식은 부모가 준 함수를 통해서만 부모의 state를 바꿀 수 있습니다. setter 자체를 넘겨도 되고(onSelect={setColor}), 더 많은 일을 하는 함수를 넘겨도 됩니다(onOpen={() => setOpenIndex(1)}). prop 이름을 setSelected가 아니라 onSelect나 onChange 같은 이벤트 스타일로 지으면, 자식은 부모가 값을 어떻게 저장하는지 모르게 되므로 부모가 나중에 자식을 건드리지 않고 그 방식을 바꿀 수 있습니다.

ColorPicker와 Preview는 서로 대화하지 않습니다. 선택기가 선택을 위로 알리고, App이 그것을 저장하고, 새 값이 둘 모두에게 아래로 흐릅니다.

state를 끌어올리지 말아야 할 때

끌어올리기에는 비용이 있습니다. state가 부모에 있으면 변경될 때마다 부모가 렌더링되고, 기본적으로 그 state를 쓰지 않는 자식까지 모든 자식이 렌더링됩니다. 가장 가까운 공통 부모까지만 끌어올리고, 컴포넌트 하나만 쓰는 state는 그 컴포넌트 안에 두세요.

입력창에 입력하면서 콘솔을 보세요. 키를 누를 때마다 SearchBox만 렌더링됩니다. 이제 query를 App으로 올리고 SearchBox에 props로 넘겨 보세요. 그러면 목록이 query를 쓰지 않는데도 키를 누를 때마다 ProductList도 로그를 남깁니다. 목록이 query로 필터링한다면 두 컴포넌트가 같은 값에 의존하게 되므로 끌어올리는 것이 옳은 선택입니다.

물어야 할 질문은 "이 값을 누가 읽어야 하는가?"입니다. 답이 컴포넌트 하나라면 state는 그곳에 남습니다. 여러 개라면 그것들의 가장 가까운 공통 부모로 갑니다.

너무 높이 끌어올렸을 때

때로는 가장 가까운 공통 부모가 트리의 훨씬 위에 있어서, 값을 전달만 하는 여러 컴포넌트를 거쳐 넘겨야 합니다. 이것이 prop drilling입니다. props 몇 단계는 괜찮고 따라가기도 쉽습니다. 같은 값이 여러 단계를 거쳐 가거나, 거의 모든 컴포넌트가 그 값을 필요로 한다면(로그인한 사용자, 테마, 언어), 직접 넘기지 말고 useContext로 읽으세요. 컨텍스트는 값이 자식에게 도달하는 방식을 바꿀 뿐이고, state 자체는 여전히 한 부모에 있으므로 여전히 끌어올린 상태입니다.

자주 묻는 질문

React에서 상태 끌어올리기란 무엇인가요?

state를 사용하는 컴포넌트들에서 그것들의 가장 가까운 공통 부모로 옮기는 것입니다. 부모가 state를 소유하고, 값과 그 값을 바꾸는 함수를 props로 자식에게 내려 줍니다.

React에서 형제 컴포넌트 두 개가 state를 공유하려면 어떻게 하나요?

형제는 서로의 state를 읽을 수 없습니다. state를 공유하는 부모에 두고, 값을 둘 모두에게 넘기고, 값을 바꾸는 쪽에는 setter(또는 onChange 같은 핸들러)를 넘기세요. 그러면 두 형제가 같은 값으로 렌더링합니다.

자식 컴포넌트는 부모의 state를 어떻게 업데이트하나요?

부모가 onSelect={setSelected}나 onSelect={(id) => setSelected(id)}처럼 함수를 prop으로 넘기고, 자식이 그 함수를 호출합니다. state는 부모에 남아 있고, 자식은 변경을 요청할 뿐입니다.

state를 끌어올리지 말아야 할 때는 언제인가요?

state를 사용하는 컴포넌트가 하나뿐일 때입니다. 필요 이상으로 높이 끌어올리면 변경될 때마다 부모가 렌더링되고, 관심 없는 컴포넌트들에까지 props가 퍼집니다. state는 사용되는 곳에 최대한 가깝게 두세요.

state를 너무 높이 끌어올리는 것의 대안은 무엇인가요?

같은 props를 여러 단계에 걸쳐 넘기고 있다면, 대신 컨텍스트(useContext)로 공유 값을 읽거나, 그 값이 필요한 컴포넌트들이 더 가까이 있도록 구조를 바꾸세요.

Coddy 프로그래밍 언어 일러스트

Coddy로 코딩 배우기

시작하기