티스토리 뷰

컴포넌트 간 State 공유하기 : 끌어올리기

지난 시간, 상위 컴포넌트에서 아래의 컴포넌트의 상태를 관리하기 위한 전략인 끌어올리기에 대해 간략히 설명했다. 이번에는 예시를 통해 상태를 끌어올리는 과정을 확인해보자.


Accordion이 두 개의 Panel을 렌더링하는 예시

  • Accordion
    • Panel
    • Panel

Panel 컴포넌트는 콘텐츠 표시 여부를 결정하는 Boolean형 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>
    </>
  );
}

현 코드에서는 한 패널의 버튼을 눌러도, 다른 패널에는 영향을 미치지 않고 독립적인 상태임을 확인할 수 있다. (두 개 다 누르면, 둘 다 true 상태가 된다.) 그러나 이제 한 번에 하나의 패널만 열리도록 '끌어올리기'를 통해 변경해보자.

 

1. 자식 컴포넌트의 state를 제거

import { useState } from 'react';

function Panel({ title, children, isActive }) { //부모에서 전달 받는 props (2)
  //const [isActive, setIsActive] = useState(false); 제거 (1)
  return (
    <section className="panel">
      <h3>{title}</h3>
      {isActive ? (
        <p>{children}</p>
      ) : (
        <button onClick={() => setIsActive(true)}>
          Show
        </button>
      )}
    </section>
  );
}
  1. 자식 컴포넌트인 Panel에서 관리하던 상태 관리코드를 제거
  2. Panel에서 isActive 상태를 props로 지정

2. 하드 코딩된 데이터를 부모 컴포넌트로 전달

state를 올리려면, 조정하려는 두 자식 컴포넌트의 가장 가까운 공통 부모 컴포넌트에 두어야한다.

import { useState } from 'react';

export default function Accordion() {
  return (
    <>
      <h2>Almaty, Kazakhstan</h2>
      <Panel title="About" isActive={true}> //true 값을 Panel에 전달 (*)
        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}> //true 값을 Panel에 전달 (*)
        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 상태를 자식 Panel 컴포넌트에 전달한다.


3. 공통 부모에 state(상태) 추가

import { useState } from 'react';

export default function Accordion() {
  const [activeIndex, setActiveIndex] = useState(0); // 상태 추가 (1)
  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> <!-- 이벤트 핸들러를 props로 전달 (2) -->
    </>
  );
}

function Panel({
  title,
  children,
  isActive,
  onShow   
}) {
  return (
    <section className="panel">
      <h3>{title}</h3>
      {isActive ? (
        <p>{children}</p>
      ) : (
        <button onClick={onShow}> <!-- 이벤트 핸들러를 props로 전달 (2) -->
          Show
        </button>
      )}
    </section>
  );
}

1. 상태 추가: 한 번에 하나의 패널만 활성화 하기 위해서, Accrodian에서 어떤 패널이 활성화 되었는 지 추적이 필요하다. 이를 위해 인덱스 숫자를 사용한다.

2. 이벤트 핸들러를 props로 전달:Panel에서 “Show” 버튼을 클릭하면 Accordion의 활성화된 인덱스를 변경해야 한다. 그러나 activeIndex state는 Accordion 내에서 정의되었기 때문에 Panel은 값을 직접 설정할 수 없다.따라서  Accordion 컴포넌트는 이벤트 핸들러{onShow} 를 Prop으로 전달함으로써 Panel 컴포넌트가 state를 변경할 수 있음을 명시적으로 허용했다.

한 번에 한 패널만 활성화되고 있다.

 

각각의 고유한 state(상태)에 대해 어떤 컴포넌트가 “소유”할지 고를 수 있다. 이 원칙은 또한 “단일 진리의 원천” 을 갖는 것으로 알려져 있는데, 이는 모든 state가 한 곳에 존재한다는 의미가 아니라 그 정보를 가지고 있는 특정 컴포넌트가 있다는 것을 뜻한다. 컴포넌트 간의 공유된 state를 중복하는 대신 그들의 공통 부모로 끌어올리고 필요한 자식에게 전달하자.

작업이 진행되면서 애플리케이션은 계속 변한다. 각 state가 어디에 “생존”해야 할지 고민하는 동안 state를 아래로 이동하거나 다시 올리는 것은 흔히 있는 일이다.

 

요약

  • 두 컴포넌트를 조정하고 싶을 때 state를 그들의 공통 부모로 이동한다.
  • 그리고 공통 부모로부터 props를 통해 정보를 전달한다.
  • 마지막으로 이벤트 핸들러를 전달해 자식에서 부모의 state를 변경할 수 있도록 한다.
  • 컴포넌트를 (props로부터) “제어”할지 (state로부터) “비제어” 할지 고려하면 유용하다.
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
글 보관함