React 21. Sharing State Between Components

뚜루미·2024년 3월 27일

React

목록 보기
21/39
post-thumbnail

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

Lifting state up by example

이 예에서는 상위 컴포넌트 Accordion 은 두 개의 별도의 Panel 을 렌더링합니다.

  • Accordion
    • Panel
    • Panel

각 컴포넌트 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>
    </>
  );
}

한 패널을 눌러도 다른 패널에는 영향을 주지 않습니다. 두 패널은 독립적이기 때문입니다.

하지만 이제 언제든지 한 패널만 확장되도록 변경하고 싶다고 가정해 보겠습니다. 그 디자인이라면 두 번째 패널을 확장하면 첫 번째 패널은 축소될 것입니다.

두 패널을 조정하려면 세 단계에 거쳐 상위 컴포넌트로 상태를 올려야 합니다.

  1. 하위 컴포넌트에서 상태를 제거합니다.
  2. 공통 상위 컴포넌트로부터 하드코딩된 데이터를 전달합니다.
  3. 공통 상위 컴포넌트에 상태를 추가하고 이를 이벤트 핸들러와 함께 전달합니다.

이렇게 하면 컴포넌트 Accordion 이 두 가지 Panel 을 모두 조정하고 한 번에 하나씩만 확장할 수 있습니다.

Step 1: Remove state from the child components

상위 컴포넌트에 Panel 에 대한 제어권을 부여합니다. 이는 상위 컴포넌트가 prop 대신 isActivePanel 에 전달한다는 것을 뜻합니다. 먼저 Panel 컴포넌트에서 해당 라인을 지우겠습니다.

const [isActive, setIsActive] = useState(false);

대신에, Panel 의 prop 리스트에 isActive 를 추가합니다.

function Panel({ title, children, isActive }) {

현재, Panel 의 상위 컴포넌트는 prop을 전달함으로써 isActive 를 제어할 수 있게 되었습니다. 반대로, Panel 컴포넌트는 isActive 에 대한 제어권을 갖고 있지 않게 됩니다.

Step 2: Pass hardcoded data from the common parent

상태를 위로 올리기 위해, 조정하려는 두 하위 컴포넌트 모두에서 가장 가까운 공통 상위 컴포넌트를 찾아야 합니다.

이 예에서는 컴포넌트 Accordion 이 두 Panel 위에 있고 해당 prop을 제어할 수 있으므로 Panel 이 활성화되어 있는 근원이 됩니다. 컴포넌트 Accordion 은 하드코딩된 isActive 값을 두 Panel 에 전달합니다.

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 를 수정하여 결과를 변경할 수 있습니다.

Step 3: Add state to the common parent

상태를 올리면 상태를 저장하는 내용의 특성이 변경되는 경우가 많습니다.

이 경우 한 번에 하나의 패널만 활성화되어야 합니다. 이는 공통 컴포넌트 Accordion 이 어느 패널이 활성 패널인지 추적해야 함을 의미합니다. boolean 값을 사용하는 대신 상태 변수로 활성 패널의 인덱스의 숫자를 사용할 수 있습니다.

const [activeIndex, setActiveIndex] = useState(0);

activeIndex0 이라면, 첫 번째 패널이 활성화되어 있다는 것을 뜻하고, activeIndex1 이라면, 두 번째 패널이 활성화되어 있다는 것을 뜻합니다.

Panel 의 “Show” 버튼을 클릭하면 Accordion 의 활성 인덱스가 변하게 됩니다. PanelactiveIndex 상태를 직접적으로 변경할 수 없습니다. 이는 Accordion 내부에 정의되어 있기 때문입니다. 컴포넌트 Accordion 은 명시적으로 컴포넌트Panel 에 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>
  );
}

Panel 내부의 <button> 은 클릭 이벤트핸들러로 prop onShow 를 사용합니다.

이 것으로 상태 올리기는 완료되었습니다. 상태를 공통 상위 컴포넌트로 이동시켜 두 패널을 조정할 수 있게 되었습니다. 두 개의 “표시됨” 플래그 대신 활성 인덱스를 사용하면 한번에 하나의 패널만 활성화됩니다. 그리고 이벤트 핸들러를 자식에게 전달하면 부모의 상태를 변경할 수 있습니다.

Controlled and uncontrolled components

일부 지역 상태가 “제어되지 않음”인 컴포넌트를 호출하느 것은 일반적입니다. 예를 들어, 상태변수 isActive 가 있는 컴포넌트 Panel 은 해당 상위 컴포넌트가 패널의 활성 여부에 영향을 줄 수 없기 때문에 제어되지 않습니다.

반대로, 컴포넌트이 중요한 정보가 자체 지역 상태가 아닌 props에 의해 구동되는 경우 컴포넌트가 “제어된다”고 말할 수 있습니다. 이를 통해 상위 컴포넌트가 해당 동작을 완전히 지정할 수 있습니다. props isActive 가 있는 최종 컴포넌트 Panel 은 컴포넌트 Accordion 에 의해 제어됩니다.

제어되지 않는 컴포넌트는 구성이 덜 필요하므로 상위 컴포넌트 내에서 사용하기 더 쉽습니다. 그러나 함께 조정하려는 경우 유연성이 떨어집니다. 제어되는 컴포넌트는 유연하지만 props를 사용하여 완전히 구성하기 때문에 상위 컴포넌트가 필요합니다.

실제로 “제어됨”과 “제어되지 않음”은 엄격한 기술 용어가 아닙니다. 각 컴포넌트에는 일반적으로 지역 상태와 prop이 혼합되어 있습니다. 그러나 이는 컴포넌트의 설계 방식과 컴포넌트가 제공하는 기능에 대해 설명하는 유용한 방법입니다.

컴포넌트를 작성할 때 그 안의 어떤 정보를 제어해야 하는지(props를 통해), 어떤 정보를 제어하지 않아야 하는지(state를 통해) 고려하세요. 하지만 언제는 리팩토링할 수 있습니다.

A single source of truth for each state

React 어플리케이션에서는 많은 컴포넌트가 자체 상태를 갖습니다. 일부 상태는 입력과 같이 리프 컴포넌트 가까이에 있을 수 있습니다. 다른 상태는 앱 상단에 더 가까이 있을 수 있습니다. 예를 들어, 클라이언트 측 라우팅 라이브러리도 일반적으로 현재 경로를 React 상태에 저장하고 이를 props로 전달하여 구현됩니다.

각각의 고유한 상태에 대해 이를 “소유”하는 컴포넌트를 선택합니다. 이 원칙은 “단일 진실 공급원(Single Source Of Truth)”를 갖는 것으로도 알려져 있습니다. 이는 모든 상태가 한 곳에 있다는 것을 의미하지는 않습니다. 그러나 각 상태에는 해당 정보를 보유하는 특정 컴포넌트가 있습니다. 컴포넌트 간에 공유 상태를 복제하는 대신 이를 공통 공유 상위 컴포넌트로 올리고 필요한 하위 항목에 전달합니다.

작업을 진행하면 앱이 변경됩니다. 상태의 각 부분의 위치를 파악하는 동안 상태를 아래로 이동하거나 위로 이동하는 것은 일반적입니다.

0개의 댓글