SPACE RUMI

Hi, I am rumi. Let's Splattack!

[STUDY] 스터디/React Docs

React docs beta 한글 번역 : Sharing State Between Components - 컴포넌트 간 상태 공유

백루미 2023. 3. 13. 22:15
반응형

컴포넌트 간 상태 공유

두 컴포넌트의 상태가 항상 함께 변경되기를 원할 때가 있습니다. 이렇게 하려면 두 컴포넌트에서 state를 제거하고 가장 가까운 공통 부모로 이동한 다음 props를 통해 전달하면 됩니다. 이를 상태 리프팅이라고 하며, React 코드를 작성할 때 가장 흔히 하는 작업 중 하나입니다.

 

학습 내용

  • 컴포넌트를 올려보내서 컴포넌트 간에 상태를 공유하는 방법
  • 제어되는 컴포넌트와 제어되지 않는 컴포넌트

 

리프팅 상태 예시 

이 예제에서는 부모 아코디언 컴포넌트가 두 개의 개별 패널을 렌더링합니다:

  • Accordion
    - Panel
    - Panel

각 Panel 컴포넌트에는 콘텐츠가 표시되는지 여부를 결정하는 boolean isActive 상태가 있습니다.

두 Panel 모두에 대해 표시 버튼을 누릅니다:

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>
    </>
  );
}

한 Panel의 버튼을 눌러도 다른 Panel에 영향을 미치지 않는 독립적인 방식에 주목하세요.

 

하지만 이제 특정 시간에 하나의 Panel만 확장되도록 변경하고 싶다고 가정해 보겠습니다. 이 디자인에서는 두 번째 Panel을 확장하면 첫 번째 Panel이 접혀야 합니다. 어떻게 할 수 있을까요?

이 두 Panel을 조정하려면 세 단계에 걸쳐 부모 컴포넌트로 "상태를 끌어올려야" 합니다:

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

이렇게 하면 아코디언 컴포넌트가 두 패널을 조정하고 한 번에 하나씩만 확장할 수 있습니다.

 

1단계: 하위 컴포넌트에서 상태 제거 

부모 컴포넌트에 Panel의 isActive에 대한 제어권을 부여합니다. 즉, 부모 컴포넌트가 isActive를 대신 Panel에 props로 전달합니다. 먼저 Panel 컴포넌트에서 이 줄을 제거하세요:

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

대신 Panel의 props 목록에 isActive를 추가합니다:

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

이제 Panel의 부모 컴포넌트가 isActive를 props로 전달하여 제어할 수 있습니다.
반대로, 이제 Panel 컴포넌트는 isActive 값을 제어할 수 없습니다. 부모 컴포넌트에 달려 있습니다!

 

2단계: 공통 부모에서 하드코딩된 데이터 전달하기 

상태를 올리려면 조정하려는 두 자식 컴포넌트에서 가장 가까운 공통 부모 컴포넌트를 찾아야 합니다:

  • Accordion (가장 가까운 공통 부모)
    - Panel
    - Panel

이 예제에서는 아코디언 컴포넌트입니다. 두 패널 위에 있고 props를 제어할 수 있으므로 현재 활성화된 Panel에 대한 "진짜 소스"가 됩니다. Accordion 컴포넌트가 두 Panel 모두에 하드코딩된 isActive 값(예: true)을 전달하도록 합니다:

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 값을 편집하고 화면에서 결과를 확인합니다.

3단계: 공통 부모에 상태 추가 

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

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

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

activeIndex가 0이면 첫 번째 패널이 활성화되고 1이면 두 번째 패널이 활성화됩니다.

어느 패널에서든 "Show" 버튼을 클릭하면 Accordion의 active 인덱스가 변경됩니다. active 인덱스 상태는 Accordion 내부에 정의되어 있기 때문에 Panel에서 직접 설정할 수 없습니다. Accordion 컴포넌트는 이벤트 핸들러를 프로퍼티로 전달하여 Panel 컴포넌트가 상태를 변경할 수 있도록 명시적으로 허용해야 합니다:

<>
  <Panel
    isActive={activeIndex === 0}
    onShow={() => setActiveIndex(0)}
  >
    ...
  </Panel>
  <Panel
    isActive={activeIndex === 1}
    onShow={() => setActiveIndex(1)}
  >
    ...
  </Panel>
</>

이제 Panel 내부의 <Button>은 클릭 이벤트 핸들러로 onShow props를 사용합니다:

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을 조정할 수 있습니다. 두 개의 "표시됨" 플래그 대신 active 인덱스를 사용하면 주어진 시간에 하나의 Panel만 활성화되도록 할 수 있었습니다. 그리고 이벤트 핸들러를 자식에게 전달하면 자식이 부모의 상태를 변경할 수 있었습니다.

 

Deep Dive - 제어 및 비제어 구성 요소

일부 로컬 상태를 가진 컴포넌트를 "uncontrolled(제어되지 않음)"이라고 부르는 것이 일반적입니다. 예를 들어, isActive 상태 변수가 있는 원래 Panel 컴포넌트는 부모가 Panel의 활성 여부에 영향을 줄 수 없기 때문에 제어되지 않습니다.

반대로 컴포넌트의 중요한 정보가 자체 로컬 상태가 아닌 props에 의해 구동되는 경우 컴포넌트가 "제어"된다고 말할 수 있습니다. 이렇게 하면 부모 컴포넌트가 그 동작을 완전히 지정할 수 있습니다. 최종 Panel 컴포넌트에는 isActive props가 있으며, Accordion 컴포넌트에 의해 제어됩니다.

제어되지 않는 컴포넌트는 구성이 덜 필요하기 때문에 부모 컴포넌트 내에서 사용하기가 더 쉽습니다. 하지만 함께 조정하려는 경우 유연성이 떨어집니다. 제어 컴포넌트는 최대한의 유연성을 제공하지만 부모 컴포넌트가 props로 완전히 구성해야 합니다.

실제로 "제어"와 "제어되지 않음"은 엄격한 기술 용어가 아니며, 각 컴포넌트에는 일반적으로 로컬 상태와 props가 어느 정도 혼합되어 있습니다. 하지만 컴포넌트가 어떻게 설계되고 어떤 기능을 제공하는지 설명하는 데 유용한 용어입니다.

컴포넌트를 작성할 때는 컴포넌트에서 어떤 정보를 제어해야 하는지(props를 통해), 어떤 정보를 제어하지 않아야 하는지(상태를 통해) 고려하세요. 하지만 나중에 언제든지 마음을 바꾸고 리팩토링할 수 있습니다.

 

각 상태에 대한 단일 진실 소스 

React 애플리케이션에서는 많은 컴포넌트가 고유한 state를 갖습니다. 일부 state는 입력과 같이 leaf 컴포넌트(트리의 맨 아래에 있는 컴포넌트)에 가깝게 "live"할 수 있습니다. 다른 상태는 앱의 상단에 더 가깝게 'live'할 수 있습니다. 예를 들어 클라이언트 측 라우팅 라이브러리조차도 일반적으로 현재 경로를 React state에 저장하고 props로 전달하여 구현됩니다!

각각의 고유한 state에 대해 "소유"하는 컴포넌트를 선택하게 됩니다. 이 원칙을 "단일 소스"를 갖는다고도 합니다. 모든 상태가 한 곳에 존재한다는 뜻이 아니라, 각 상태마다 해당 정보를 보유하는 특정 컴포넌트가 있다는 뜻입니다. 컴포넌트 간에 공유 상태를 복제하는 대신 공통 공유 부모로 끌어올려서 필요한 자식에게 전달합니다.

앱은 작업하면서 변경됩니다. 각 상태의 '위치'를 파악하는 동안 상태를 아래로 이동하거나 백업하는 것이 일반적입니다. 이 모든 것이 프로세스의 일부입니다!

몇 가지 컴포넌트를 사용해 실제로 어떤 느낌인지 알아보려면 React에서 생각하기를 읽어보세요.

 

요약

  • 두 컴포넌트를 조정하려면 두 컴포넌트의 상태를 공통 부모로 이동하세요.
  • 그런 다음 공통 부모로부터 props를 통해 정보를 전달합니다.
  • 마지막으로 이벤트 핸들러를 전달하여 자식이 부모의 상태를 변경할 수 있도록 합니다.
  • 컴포넌트를 "제어되는"(props에 의해 구동되는) 또는 "제어되지 않는"(상태에 의해 구동되는) 것으로 간주하는 것이 유용합니다.

 

과제 1 / 2: 동기화된 입력 

이 두 입력은 독립적입니다. 하나의 입력을 편집하면 다른 입력도 동일한 텍스트로 업데이트되어야 하며, 그 반대의 경우도 마찬가지입니다.

https://codesandbox.io/s/synced-inputs-py7rdj?file=/App.js

 

과제 2/2: 목록 필터링 

이 예제에서 SearchBar에는 텍스트 입력을 제어하는 자체 쿼리 상태가 있습니다. 이 컴포넌트의 상위 필터링 가능 목록 컴포넌트는 항목 목록을 표시하지만 검색 쿼리는 고려하지 않습니다.

filterItems(foods, query) 함수를 사용하여 검색 쿼리에 따라 목록을 필터링합니다. 변경 사항을 테스트하려면 입력에 "s"를 입력하면 목록이 "스시", "케밥", "딤섬"으로 필터링되는지 확인합니다.

filterItems는 이미 구현되어 임포트되어 있으므로 직접 작성할 필요가 없습니다!

https://codesandbox.io/s/ancient-cookies-g5n2fz?file=/App.js

 

요약)

1. 여러 컴포넌트를 조정하려면, 하위 컴포넌트의 상태를 공통 부모로 이동해서 컨트롤한다.
2. 그 정보를 하위 컴포넌트에 props로 전달한다.
반응형