
때로는 두 컴포넌트의 상태가 항상 함께 변경되기를 원할 수도 있다. 이를 수행하려면 둘 다에서 상태를 제거하고 가장 가까운 공통 상위 항목으로 옮긴 다음 props를 통해 상태를 전달한다. 이는 상태 올리기(lifting state up)라 알려져 있으며, 리액트 코드를 작성하는 가장 일반적인 작업 중 하나다.

이 예에서 상위 Accordion 컴포넌트는 두 개의 별도 Panel 을 렌더링한다.
AccordionPanelPanel각 Panel 컴포넌트에는 콘텐츠 표시 여부를 결정하는 부울 isActive 상태가 있다.
두 패널 모두에 대해 표시 버튼을 누른다.
import { useState } from 'react';
function Panel({ title, children }) {
const [isActive, setIsActive] = useState(false);
return (
<section className="panel">
<h3>{title}</h3>
{isActive ? (
<p>{children}</p>
) : (
<button onClick={() => setIsActive(true)}>
Show
</button>
)}
</section>
);
}
export default function Accordion() {
return (
<>
<h2>Almaty, Kazakhstan</h2>
<Panel title="About">
With a population of about 2 million, Almaty is Kazakhstan's largest city. From 1929 to 1997, it was its capital city.
</Panel>
<Panel title="Etymology">
The name comes from <span lang="kk-KZ">алма</span>, the Kazakh word for "apple" and is often translated as "full of apples". In fact, the region surrounding Almaty is thought to be the ancestral home of the apple, and the wild <i lang="la">Malus sieversii</i> is considered a likely candidate for the ancestor of the modern domestic apple.
</Panel>
</>
);
}
한 패널의 버튼을 눌러도 다른 패널에는 영향을 주지 않는다. 두 패널은 독립적이다.

하지만 이제 주어진 시간에 하나의 패널만 확장되도록 변경하고 싶다고 가정하자. 해당 디자인에서는 두 번째 패널을 확장하면 첫 번째 패널이 축소된다. 어떻게 할까?
이 두 패널을 조정하려면 세 단계를 거쳐 상위 컴포넌트로 “상태를 올려야”한다.
이렇게 하면 Accordion 컴포넌트가 두 Panel 을 모두 조정하고 한 번에 하나씩만 확장할 수 있다.
Panel 의 isActive 에 대한 제어권을 상위 컴포넌트에 부여한다. 이는 상위 컴포넌트가 isActive 를 Panel 에 prop으로 대신 전달한다는 의미다. 먼저 Panel 컴포넌트에서 다음 줄을 제거해라.
const [isActive, setIsActive] = useState(false);
And instead, add isActive to the Panel’s list of props:
대신 Panel 의 prop 목록에 isActive 를 추가해라.
function Panel({ title, children, isActive }) {
이제 Panel 의 상위 컴포넌트는 isActive 를 prop으로 전달하여 제어할 수 있다. 반대로 이제 Panel 컴포넌트는 isActive 값을 제어할 수 없다. 이제 상위 컴포넌트에 달려있다.
상태를 해제하려면 조정하려는 두 하위 컴포넌트 모두에서 가장 가까운 공통 상위 컴포넌트를 찾아야 한다.
Accordion (가장 가까운 공통 부모)PanelPanel이 예에서는 Accordion 컴포넌트다. 두 패널 위에 있고 prop을 제어할 수 있으므로 현재 패널이 활성화된 “진실의 소스”가 된다. Accordion 컴포넌트가 하드코딩된 isActive 을 두 패널 모두에게 전달하도록 만든다.
import { useState } from 'react';
export default function Accordion() {
return (
<>
<h2>Almaty, Kazakhstan</h2>
<Panel title="About" isActive={true}>
With a population of about 2 million, Almaty is Kazakhstan's largest city. From 1929 to 1997, it was its capital city.
</Panel>
<Panel title="Etymology" isActive={true}>
The name comes from <span lang="kk-KZ">алма</span>, the Kazakh word for "apple" and is often translated as "full of apples". In fact, the region surrounding Almaty is thought to be the ancestral home of the apple, and the wild <i lang="la">Malus sieversii</i> is considered a likely candidate for the ancestor of the modern domestic apple.
</Panel>
</>
);
}
function Panel({ title, children, isActive }) {
return (
<section className="panel">
<h3>{title}</h3>
{isActive ? (
<p>{children}</p>
) : (
<button onClick={() => setIsActive(true)}>
Show
</button>
)}
</section>
);
}
Accordion 컴포넌트에서 하드코딩된 isActive 값을 편집해 보고 화면에서 결과를 확인해라.
상태를 올리면 상태로 저장하는 내용의 성격이 바뀌는 경우가 많다.
이 경우 한 번에 하나의 패널만 활성화되어야 한다. 이는 Accordion 공통 상위 컴포넌트가 어느 패널이 활성 패널인지 추적해야 함을 의미한다. boolean 값 대신 상태 변수에 대한 활성 Panel 의 인덱스로 숫자를 사용할 수 있다.
const [activeIndex, setActiveIndex] = useState(0);
When the activeIndex is 0, the first panel is active, and when it’s 1, it’s the second one.
activeIndex 가 0 이면 첫 번째 패널이 활성화되고, 1 이면 두 번째 패널이 활성화된다.
패널 중 하나에서 “Show” 버튼을 클릭하면 Accordion 의 활성 인덱스를 변경해야 한다. Panel 은 Accordion 내부에 정의되어 있으므로 activeIndex 상태를 직접 설정할 수 없다. Accordion 컴포넌트는 이벤트 핸들러를 prop으로 전달하여 Panel 컴포넌트가 상태를 변경할 수 있도록 명시적으로 허용해야 한다.
<>
<Panel
isActive={activeIndex === 0}
onShow={() => setActiveIndex(0)}
>
...
</Panel>
<Panel
isActive={activeIndex === 1}
onShow={() => setActiveIndex(1)}
>
...
</Panel>
</>
Panel 내부의 <button> 은 onShow prop을 클릭 이벤트 핸들러로 사용한다.
import { useState } from 'react';
export default function Accordion() {
const [activeIndex, setActiveIndex] = useState(0);
return (
<>
<h2>Almaty, Kazakhstan</h2>
<Panel
title="About"
isActive={activeIndex === 0}
onShow={() => setActiveIndex(0)}
>
With a population of about 2 million, Almaty is Kazakhstan's largest city. From 1929 to 1997, it was its capital city.
</Panel>
<Panel
title="Etymology"
isActive={activeIndex === 1}
onShow={() => setActiveIndex(1)}
>
The name comes from <span lang="kk-KZ">алма</span>, the Kazakh word for "apple" and is often translated as "full of apples". In fact, the region surrounding Almaty is thought to be the ancestral home of the apple, and the wild <i lang="la">Malus sieversii</i> is considered a likely candidate for the ancestor of the modern domestic apple.
</Panel>
</>
);
}
function Panel({
title,
children,
isActive,
onShow
}) {
return (
<section className="panel">
<h3>{title}</h3>
{isActive ? (
<p>{children}</p>
) : (
<button onClick={onShow}>
Show
</button>
)}
</section>
);
}
이것으로 상태 끌어 올리기 완료다. 상태를 공통 상위 컴포넌트로 이동하면 두 패널을 조정할 수 있었다. 두 개의 “is shown” 플래그 대신 활성 인덱스를 사용하면 주어진 시간에 하나의 패널만 활성화된다. 그리고 이벤트 핸들러를 자식에게 전달하면 자식이 부모의 상태를 변경할 수 있다.

Controlled and uncontrolled components
일부 로컬 상태가 “제어되지 않음”인 컴포넌트를 호출하는 것이 일반적이다. 예를 들어, isActive 상태 변수가 있는 원래 Panel 컴포넌트는 해당 상위 패널이 활성상태인지 여부에 영향을 줄 수 없기 때문에 제어되지 않는다.
대조적으로, 컴포넌트의 중요한 정보가 자체 로컬 상태가 아닌 props에 의해 구동되는 경우 컴포넌트가 “제어된다”고 말할 수 있다. 이를 통해 상위 컴포넌트가 해당 동작을 완전히 지정할 수 있다. isActive prop이 있는 최종 Panel 컴포넌트는 Accordion 컴포넌트에 의해 제어된다.
제어되지 않는 컴포넌트는 구성이 덜 필요하므로 상위 컴포넌트 내에서 사용하기가 더 쉽다. 하지만 함께 조정하려는 경우 유연성이 떨어진다. 제어되는 컴포넌트는 최대한 유연하지만 props를 사용하여 완전히 구성하려면 상위 컴포넌트가 필요하다.
실제로 “제어됨”과 “제어되지 않음”은 엄격한 기술 용어가 아니다. 각 컴포넌트에는 일반적으로 로컬 상태와 소품이 혼합되어 있다. 그러나 이는 컴포넌트의 설계 방식과 컴포넌트가 제공하는 기능에 대해 설명하는 유용한 방법이다.
컴포넌트를 작성할 때 그 안의 어떤 정보를 제어해야 하는지(props를 통해), 어떤 정보를 제어하지 않아야 하는지(state를 통해) 고려해라. 하지만 언제든지 마음을 바꾸고 나중에 리팩토링할 수 있다.
리액트 애플리케이션에서는 많은 컴포넌트가 자체 상태를 갖는다. 일부 상태는 입력과 같이 리프 컴포넌트(트리 하단에 있는 컴포넌트) 가까이에 “살아”있을 수 있다. 다른 상태는 앱 상단에 더 가깝게 “실시간”일 수 있다. 예를 들어, 클라이언트 측 라우팅 라이브러리도 일반적으로 현재 경로를 리액트 상태에 저장하고 이를 props로 전달하여 구현된다.
각각의 고유한 상태에 대해 이를 “소유”하는 컴포넌트를 선택한다. 이 원칙은 “single source of truth”를 갖는 것으로도 알려져 있다. 이는 모든 상태가 한 곳에 있다는 것을 의미하지는 않는다. 그러나 각 상태에는 해당 정보를 보유하는 특정 컴폰넌트가 있다. 컴포넌트 간에 공유 상태를 복제하는 대신 이를 공통 공유 상위로 올리고 필요한 하위 항목에 전달한다.
작업을 진행하면 앱이 변경된다. 상태의 각 부분이 어디에 있는지 파악하는 동안 상태를 아래로 이동하거나 위로 이동하는 것이 일반적이다. 이것은 모두 과정의 일부다.
몇 가지 추가 컴포넌트를 사용하여 이것이 실제로 어떤 느낌인지 확인하려면 Thinking in React를 읽어봐라.