때때로 두 컴포넌트의 상태를 항상 같이 바꾸어야할 때가 있어요. 그렇게 하려면 양쪽에서 상태를 제거하고 가장 가까운 공통 부모로 이동한 후에 props를 통하여 필요한 상태를 아래로 내려주어야해요. 이는 상태 끌어올리기로 알려져 있고 리액트로 코드를 쓸 때 가장 자주 사용할 것들 중 하나에요.
이 페이지에서는
- 상태를 끌어올려서 컴포넌트 간에 어떻게 공유하는지
- 제어 컴포넌트와 비제어 컴포넌트는 무엇인지
를 알아볼 거예요.
Lifting state up by example | 예시를 통해 상태 끌어올리기
이 예시에서 부모 컴포넌트인 Accordion
컴포넌트는 두 개의 다른 Panel
을 렌더링해요.
Accordion
Panel
Panel
각각의 Panel
컴포넌트는 콘텐츠를 볼 수 있는지를 결정하는 isActive
라는 불리안 타입의 상태를 갖고 있어요.
양 쪽 패널에서 Show 버튼을 눌러보세요.
한 패널의 버튼을 눌러도 다른 패널에 영향을 주지 않는다는 점을 알 수 있어요. 이들은 독립적이죠.


초기에는 각 Panel
의 isActive
상태는 false
이기 때문에 모두 닫혀있어요.
둘 중 어떤 Panel
의 버튼을 눌러도
해당 Panel
의 isActive
상태만 업데이트 돼요.
그러나 이제는 한 번에 하나의 패널만 펼처지도록 바꾼다고 생각할게요. 이 디자인에서, 두 번째 패널을 확장하면 첫 번째가 닫혀야해요. 어떻게 할까요?
이 두 패널을 조정하려면 세 단계를 통해 부모 컴포넌트로 "상태를 끌어올려야" 해요.
- 자식 컴포넌트에서 상태를 제거하세요.
- 하드코딩된 데이터를 공통 부모에서 전달하세요.
- 공통 부모에 상태를 추가하고 이벤트 핸들러와 함께 전달하세요.
이렇게 하면 Accordion
컴포넌트는 양 Panel
을 조정하고 한 번에 하나만을 펼쳐줄 거예요.
Step 1: Remove state from the child components | 1단계: 자식 컴포넌트에서 상태 제거하기
Panel
의 isActive
를 컨트롤할 권한을 부모 컴포넌트에 줄 거예요. 즉, 부모 컴포넌트가 isActive
를 Panel
에 prop으로 전달할 거예요. Panel
컴포넌트에서 이 줄을 제거하는 것부터 시작하세요.
const [isActive, setIsActive] = useState(false);
대신, isActive
를 Panel
의 props 목록에 추가하세요.
function Panel({ title, children, isActive }) {}
이제 Panel
의 부모 컴포넌트는 isActive
를 prop으로 내려서 관리할 수 있어요. 반대로 Panel
컴포넌트는 이제 isAcitve
의 값을 관리할 수 없어요. 이는 부모 컴포넌트에 달려있어요.
Step 2: Pass hardcoded data from the common parent | 2단계: 공통 부모에서 하드코딩된 데이터 전달하기
상태를 끌어올리기 위해서는 묶으려는 양 자식 컴포넌트의 가장 가까운 공통 부모 컴포넌트에 위치해야해요.
Accordion
(가장 가까운 공통 부모)Panel
Panel
이 예시는 Accordion
컴포넌트에요. 이 컴포넌트는 두 개의 패널의 상위에 있고 이들의 props를 관리할 수 있기 때문에 현재 활성화된 패널의 "진실 공급원"이에요. Accordion
컴포넌트가 하드코딩된 isActive
값(이를테면 true
)을 각 패널로 전달하도록 만드세요.
Accordion
컴포넌트에서 하드코딩된 isActive
값을 수정하고 화면에서 결과를 확인하세요.
Step 3: Add state to the common parent | 3단계: 공통 부모에서 상태 추가하기
상태를 끌어올리면 종종 상태로 저장하고 있는 상태의 본질을 변경해요.
이런 경우에서 한 번에 하나의 패널만 활성화 해야해요. 이는 공통 부모 컴포넌트인 Accordion
은 어떤 패널이 활성화된 패널인지를 추척재향한다는 것이에요. boolean
값 대신 활성화된 Panel
의 인덱스를 상태 변수로 사용할 수 있어요.
const [activeIndex, setActiveIndex] = useState(0);
activeIndex
가 0
이라면 첫 번째 패널이 활성화되고, 1
이라면 두번째가 활성화 돼요.
"Show" 버튼을 양쪽 Panel
에서 누르면 Accordion
에서 활성화된 박스가 변해야해요. Panel
은 activeIndex
상태를 직접적으로 수정할 수 없어요. 왜냐하면 Accordion
의 내부에서 정의되었기 때문이에요. Accordion
컴포넌트는 Panel
컴포넌트가 prop으로 이벤트 핸들러를 아래로 전달해서 상태를 바꾸는 것을 명시적으로 허용해줘요.
<>
<Panel
isActive={activeIndex === 0}
onShow={() => setActiveIndex(0)}
>
...
</Panel>
<Panel
isActive={activeIndex === 1}
onShow={() => setActiveIndex(1)}
>
...
</Panel>
</>
Panel
내부의 <button>
은 클릭의 이벤트 핸들러로 onShow
prop을 사용할 거예요.
이제 상태 끌어올리기가 끝났어요! 상태를 공통 부모 컴포넌트로 옮기는 것은 두 개의 패널을 함꼐 조종하도록 해줘요. 두 개의 "is Shown" 플래그 대신 활성화된 인덱스를 사용하면 한 번에 하나의 패널만 활성화시킬 수 있어요. 그리고 이벤트 핸들러를 자식으로 내리는 것은 자식이 부모의 상태를 바꾸는 것을 허용해줘요.


처음에 Accordion
의 actvieIndex
는 0
이기 때문에 첫 번째 Panel
은 isActive = true
를 받아요.Accordion
의 actvieIndex
가 1
이 된다면 Panel
은 isActive = true
를 받아요.
제어 컴포넌트와 비제어 컴포넌트
더보기어떤 지역 상태 변수를 갖고 있는 컴포넌트를 "비제어 컴포넌트"라고 볼러요. 예를 들어
isActive
라는 상태 변수를 갖고 있던 기존의Panel
컴포넌트는 비제어 컴포넌트에요. 왜냐하면 부모 컴포넌트는 패널이 활성화된 상태인지 아닌지에 영향을 미칠 수 없기 때문이에요.
반면, 컴포넌트 안의 중요한 정보가 지역 상태가 아니라 props를 통해 관리된다면 이를 "제어 컴포넌트"라고 불러요. 이는 부모 컴포넌트가 특정 행동을 완전히 지정할 수 있도록 해줘요.
isActive
를 prop으로 가지는 최종Panel
컴포넌트는Accordion
컴포넌트로 제어돼요.
비제어 컴포넌트는 구성 요소가 덜 필요하기 때문에 부모에서 사용하기 편해요. 그러나 이들을 함께 제어하고 싶을 때는 덜 유연해요. 제어 컴포넌트는 최대한 유연하지만 부모 컴포넌트가 props를 통해 자식 컴포넌트를 완전히 구성해야해요.
실제로 "제어"와 "비제어 컴포넌트"는 엄격한 기술적 용어가 아니에요. 각 컴포넌트는 종종 지역 상태와 props를 모두 사용해요. 그러나 컴포넌트가 어떻게 디자인되고 어떤 기능을 제공하는지를 설명하는 유용하 방법이에요.
컴포넌트를 작성할 때, 어떤 정보가 (props를 통해) 제어되어야 하고 어떤 정보가 (상태를 통해) 제어되지 않아야 하는지를 생각하세요. 하지만 여러분은 언제나 마음을 바꾸고 리팩토링 할 수 있어요.
A single source of truth for each state | 각각의 상태에 대한 단일 진실 공급원
리액트 어플리케이션에서 많은 컴포넌트는 그들만의 상태를 가지고 있어요. 어떤 상태는 입력처럼 리프 컴포넌트(트리의 최하위 컴포넌트)의 근처에 "살아요." 또 다른 상태는 앱의 최상위에 더 가깝게 "살아요." 그 예로, 심지어는 클라이언트 사이드 라우팅 라이브러리도 보통 리액트 상태에 현재 경로를 저장하고 이를 props로 내리는 방법으로 구현되어 있어요.
각각의 고유한 상태 조각을 "소유할" 컴포넌트를 선택할 수 있어요. 이 원칙은 "단일 진실 공급원"이라고 알려져있어요. 이는 모든 상태가 한 곳에 있어야한다는 말이 아니라 상태의 각 부분은 정보의 조각을 가지고 있는 특정한 컴포넌트에 있어야한다는 말이에요. 컴포넌트 간에 공유되는 상태를 중복하는 대신 공통 부모로 이 상태를 끌어올리고 필요한 자식에세 내려줘요.
여러분의 앱은 여러분이 작업하는 동안 변해요. 상태의 각 부분은 어디에 "살 것인가"를 찾아내는 동안 상태를 아래로 내리거나 다시 올리는 작업은 흔한 일이에요. 이것은 모두 작업의 일부에요!
실제로 몇 개의 컴포넌트에서 이것이 어떤 느낌인지 확인하려면 리액트에서 생각하기를 읽어보세요.
Recap | 요약
- 두 개의 컴포넌트를 짝지어서 관리하고 싶다면 그들의 상태를 그들의 공통 부모로 옮기세요.
- 그리고나서 공통 부모에서 props를 통해 정보를 아래로 전달하세요.
- 마지막으로 이벤트 핸들러를 아래로 전달하세요. 그러면 자식 컴포넌트가 부모의 상태를 변경할 수 있어요.
- 컴포넌트가 (props로 제어되는) "제어 컴포넌트"인지 (상태로 제어되는) "비제어 컴포넌트"인지를 고민하면 유용해요.
Challenges | 도전 과제
1. 동기화된 입력창
아래의 두 입력창은 독립적이에요. 이들이 싱크를 유지하도록 만드세요. 하나의 입력을 수정하면 같은 텍스트로 다른 입력창도 업데이트 되어야해요. 그 반대도 마찬가지고요.
Hint
상태를 부모 컴포넌트로 끌어올려야해요.
Solution
`handleChange` 핸들러와 함께 `text` 상태 변수를 부모 컴포넌트로 옮기세요. props로 이들을 `Input` 컴포넌트로 내리세요. 이렇게 하면 싱크가 유지될 거예요.
2. 리스트 필터링하기
이 예시에서 SearchBar
는 텍스트 입력을 제어하는 상태 변수 query
를 갖고 있어요. 이것의 부모 컴포넌트인 FilterableList
컴포넌트는 항목의 List
를 보여주지만 검색 쿼리를 고려하진 않았어요.
filterItems(foods , query)
함수하여 검색 쿼리에 따라 목록을 필터링 하세요. 여러분의 변경사항을 테스트하고 싶다면 "s"를 입력창에 타이핑하여 “Sushi”, “Shish kebab”, 그리고 “Dim sum"을 포함한 목록으로 필터링 되는지 확인하세요.
filterItmes
는 이미 구현되어서 임포트 되어있기 때문에 이들을 직접 작성할 필요는 없어요!
Hint
query
와 handleChange
를 SearchBar
에서 지우고 이들은 FilterableList
로 옮기세요. 그리고 나서 SearchBar
에 query
와 onChange
props로 내리세요.
Solution
상태 변수 query
를 FilterableList
컴포넌트로 끌어올리세요. 필터링된 목록을 받기 위해filterItems(foods, query)
를 호출하고 List
로 내리세요. 이제 입력된 쿼리가 변경되면 목록에 반영돼요.
'리액트 공식문서 | React Docs > Learn > Learn React' 카테고리의 다른 글
[Managing State] Extracting State Logic into a Reducer | 리듀서로 상태 로직 추출하기 (0) | 2024.02.27 |
---|---|
[Managing State] Preserving and Resetting State | 상태 보존 및 초기화하기 (1) | 2024.02.27 |
[Managing State] Choosing the State Structure | 상태 구조 정하기 (2) | 2024.02.24 |
[Managing State] Reacting to Input with State | 상태로 입력창에 반응하기 (0) | 2024.02.22 |
[Managing State] Managing State Overview | 상태 관리하기 개요 (0) | 2024.02.22 |