본문 바로가기

나야, 리액트 스터디

[week5] State 관리하기 - State를 사용해 Input 다루기, State 구조 선택하기, 컴포넌트 간 State 공유하기, State를 보존하고 초기화하기

안녕하세요 !

웹파트 YB 한수정입니다.

솝커톤 끝나고 모두들 잘 쉬셨나요?! 저는 미리 안 해서 지금 쓰고 있는데, 저는 한 3일 아무 것도 안 하고 싶어요...... 

그치만 나리스 시작~!

 

오늘은 5주차 내용 중 가장 흥미롭게 읽었던, 컴포넌트 간 State 공유하기에 대해 정리해보려고 합니다!

 

 

state 끌어올리기를 통해 컴포넌트 간 state를 공유하는 방법

state 끌어올리기

두 개 이상의 컴포넌트가 동일한 데이터를 기반으로 동작해야 할 때, 공통 부모 컴포넌트로 상태를 옮기는 기법입니다. 이 방법을 통해 상태를 중앙에서 관리하고, 하위 컴포넌트가 props를 통해 동일한 데이터를 공유하도록 설계합니다.

 

  • 왜 state를 끌어올려야 할까..?!

여러 컴포넌트가 동일한 데이터를 공유해야 할 때, 각 컴포넌트가 독립적으로 상태를 가지면 동기화가 어려워집니다. 또한, 공통 부모 컴포넌트로 상태를 끌어올리면 모든 하위 컴포넌트가 동일한 데이터를 참조하고 변경 사항을 쉽게 반영할 수 있습니다. 

 

오늘은 색다르게 챌린지 도전하기 2번 코드로 알아보도록 하겠습니다!

import { useState } from 'react';
import { foods, filterItems } from '../data/data';

export default function FilterableList() {
  const [query, setQuery] = useState('');
  const results = filterItems(foods, query);

  function handleChange(e) {
    setQuery(e.target.value);
  }

  return (
    <>
      <SearchBar
        query={query}
        onChange={handleChange}
      />
      <hr />
      <List items={results} />
    </>
  );
}

function SearchBar({ query, onChange }) {
  return (
    <label>
      Search:{' '}
      <input
        value={query}
        onChange={onChange}
      />
    </label>
  );
}

function List({ items }) {
  return (
    <table>
      <tbody>
        {items.map(food => (
          <tr key={food.id}>
            <td>{food.name}</td>
            <td>{food.description}</td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}
  • state 끌어올리기 3단계 !!

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

각 자식 컴포넌트의 상태를 삭제합니다. 상태 관리가 분산되면 상태 동기화가 복잡해지기 때문에, 중앙에서 관리할 수 있도록 합니다.

 

🔍 코드

<SearchBar
  query={query}        // 부모가 관리하는 검색어를 props로 전달
  onChange={handleChange} // 검색어를 업데이트하는 함수도 props로 전달
/>
<List items={results} />  // 필터링된 결과를 props로 전달

 

💡코드 설명

 

  • SearchBar는 query 값을 받기만 하고, 이를 수정할 수 있도록 부모가 전달한 onChange를 호출합니다.
  • List는 필터링된 items 데이터를 props로 받아 표시만 합니다.

 

 

2. 하드 코딩된 값을 공통 부모로부터 전달합니다.

상태를 공유해야 하는 컴포넌트들의 공통 부모 컴포넌트로 상태를 옮깁니다. 공통 부모는 상태를 관리하고, 이를 자식 컴포넌트에 props로 전달합니다.

 

🔍 코드

export default function FilterableList() {
  const [query, setQuery] = useState(''); // 검색어 상태 관리
  const results = filterItems(foods, query); // 상태를 기반으로 필터링된 데이터 생성
}

 

💡코드 설명

  • FilterableList는 query 상태를 정의하고, 이를 변경할 수 있는 setQuery 함수와 함께 관리합니다.
  • filterItems 함수는 query를 기반으로 필터링된 결과를 생성합니다.
  • 이 상태와 데이터를 props를 통해 자식 컴포넌트로 전달합니다.

3.공통 부모에 state를 추가하고 이벤트 핸들러와 함께 전달합니다.

 

 

상태를 변경할 수 있는 이벤트 핸들러를 공통 부모 컴포넌트에 작성하고, 이를 자식 컴포넌트로 전달합니다.

 

🔍 코드

function handleChange(e) {
  setQuery(e.target.value); // 검색어를 상태로 업데이트
}

<SearchBar
  query={query}
  onChange={handleChange} // 이벤트 핸들러 전달
/>

 

💡코드 설명

 

  • handleChange는 검색어 입력 이벤트를 처리하는 함수로, setQuery를 호출해 상태를 업데이트합니다.
  • 이 핸들러는 SearchBar 컴포넌트에 전달되어, 사용자가 입력할 때마다 호출됩니다.

 

장점

  • 단일 소스 상태: 상태는 FilterableList에서만 관리되므로, 검색어 변경 시 모든 하위 컴포넌트가 즉시 동기화됩니다.
  • 재사용 가능성: SearchBar와 List는 상태에 독립적이기 때문에 다른 부모 컴포넌트에서도 쉽게 재사용할 수 있습니다.

 

제어 컴포넌트와 비제어 컴포넌트 

제어 컴포넌트와 비제어 컴포넌트는 컴포넌트가 데이터를 관리하는 방식에 따라 구분되는 개념입니다.

 

  • 제어 컴포넌트(Controlled Component): 컴포넌트의 데이터가 props를 통해 부모 컴포넌트에 의해 완전히 제어되는 경우를 말합니다.
    • 컴포넌트는 자체적으로 데이터를 관리하지 않고 부모가 제공한 props로 동작합니다.
    • 부모가 상태를 관리하며, 자식 컴포넌트는 이를 UI에 렌더링하거나 이벤트를 통해 상태 변경 요청을 전달합니다.
import { useState } from "react";

export default function ControlledComponent() {
  const [input, setInput] = useState("");

  const onChange = (e) => {
    setInput(e.target.value); // 상태로 입력값을 관리
  };

  const onSubmit = () => {
    console.log(input); // 상태에서 값을 가져옴
  };

  return (
    <div>
      <h3>제어 컴포넌트</h3>
      <input
        type="text"
        value={input} // 입력값을 상태로 제어
        onChange={onChange} // 입력값 변경 시 상태 업데이트
      />
      <button type="button" onClick={onSubmit}>
        제출
      </button>
    </div>
  );
}

 

  • 비제어 컴포넌트(Uncontrolled Component): 컴포넌트가 state 또는 Ref를 통해 내부적으로 데이터를 관리하는 경우를 말합니다.
    • 컴포넌트가 독립적으로 동작하며, 부모가 데이터에 접근하거나 영향을 줄 수 없습니다.
    • React의 state를 사용하지 않고 DOM 자체의 상태(예: <input>의 값)를 직접 참조합니다.
import { useRef } from "react";

export default function UncontrolledComponent() {
  const inputRef = useRef(); // DOM 요소를 직접 참조

  const onSubmit = () => {
    console.log(inputRef.current.value); // DOM 요소에서 값 읽기
  };

  return (
    <div>
      <h3>비제어 컴포넌트</h3>
      <input type="text" ref={inputRef} /> {/* 값은 DOM이 관리 */}
      <button type="button" onClick={onSubmit}>
        제출
      </button>
    </div>
  );
}

 

  • 제어 컴포넌트
    제어 컴포넌트의 값은 항상 최신값을 유지합니다. 새로운 입력 값이 생길때 마다 상태를 새롭게 갱신하고 이는 데이터와 UI에서 입력한 값이 항상 동기화됨을 알 수 있습니다.
  • 비제어 컴포넌트
    필드에서 값을 트리거 해야 값을 얻을 수 있습니다. 버튼을 클릭해 트리거 하기 전까지의 값은 변경되지 않습니다.

 

컴포넌트 간 State 공유하기 챕터를 정리하려고 했지만...! 저는 항상 나리스 코드를 제출할 때, App.jsx에서 버튼을 눌러 컴포넌트를 렌더링 하는 방식으로 제출하고 있습니다. 그래서 이번에는 React에서 컴포넌트는 상태를 독립적으로 관리한다는 내용에 대해 조금 더 알고 마무리하려고 합니다.

React에서는 컴포넌트가 자신의 UI를 렌더링하기 위해 필요한 상태를 가지고 있으며, 그 상태는 다른 컴포넌트와 영향을 주지 않습니다. 즉, 각 컴포넌트는 독립적으로 상태를 관리할 수 있다는 점이 핵심입니다.

 

상태 보존과 초기화

React는 UI 트리에서 컴포넌트의 위치를 통해 각 컴포넌트의 상태를 추적합니다. 즉, 컴포넌트가 리렌더링되거나 상태가 변경될 때, React는 이전 상태를 보존하거나 새로운 상태를 초기화합니다.

  • 상태 보존: 컴포넌트가 리렌더링될 때 상태는 그대로 유지됩니다.
  • 상태 초기화: 컴포넌트가 새로 생성되거나 key 값이 변경되면 상태가 초기화됩니다.

상태 초기화 강제하기 (key 사용)

key 값을 사용하면 컴포넌트를 새로 만들어 상태를 초기화할 수 있습니다. 예를 들어, 버튼을 클릭하여 다른 컴포넌트를 선택할 때 key를 다르게 설정하면 해당 컴포넌트는 상태가 초기화된 채로 새로 렌더링됩니다.

import { useState } from "react";
import ControlledComponent from "./components/ControlledComponent";
import Accordion from "./components/FilterableList";
import UncontrolledComponent from "./components/UncontrolledComponent";

function App() {
  const [selectedComponent, setSelectedComponent] = useState("Accordion");

  const renderComponent = () => {
    switch (selectedComponent) {
      case "Accordion":
        return <Accordion key="Accordion" />;
      case "Controlled":
        return <ControlledComponent key="Controlled" />;
      case "Uncontrolled":
        return <UncontrolledComponent key="Uncontrolled" />;
      default:
        return null;
    }
  };

  return (
    <div>
      <h1>5주차 나리스</h1>
      <div>
        <button
          style={{ marginRight: "10px" }}
          onClick={() => setSelectedComponent("Accordion")}
        >
          Accordion
        </button>
        <button
          style={{ marginRight: "10px" }}
          onClick={() => setSelectedComponent("Controlled")}
        >
          Controlled Component
        </button>
        <button onClick={() => setSelectedComponent("Uncontrolled")}>
          Uncontrolled Component
        </button>
      </div>

      <hr />
      <div>{renderComponent()}</div>
    </div>
  );
}

export default App;

key와 상태 보존에 미치는 영향

  • key: key 값이 변경되면 상태가 초기화됩니다. 예를 들어, Accordion, Controlled, Uncontrolled 버튼을 클릭할 때마다 key를 변경함으로써 해당 컴포넌트의 상태가 초기화된 채로 새로 렌더링됩니다.
  • 타입: 컴포넌트의 타입이 변경되거나 props가 달라지면 상태도 변경될 수 있습니다.

 

 

너무... 피곤함 이슈로 글의 마무리가 이런 점 죄송합니다요....

제어 컴포넌트와 비제어 컴포넌트에 대해 재밌게 읽고 찾아볼 수 있었는데, 둘의 큰 차이를 알긴 아직 어려웠던 것 같아요. 어떤 상황에서 어떤 컴포넌트를 사용하신 경험이 있는지 궁금합니다!

그리고 제어, 비제어 컴포넌트를 찾다보니 만난 throttle과 debounce에 대해서도 더 공부해보겠습니다아........

굿 밤 여러분