React를 알아가보자 - 컴포넌트 분리

Obebe·2026년 3월 24일

React

목록 보기
4/12

React에서 컴포넌트 분리

컴포넌트 분리, 이전에 Flutter에서 Widget을 따로 분리해서 사용했던 것처럼 쉬울 줄 알았다.
오만했다고 볼 수 있다. React에서 해보니 영- 감을 못잡았기 때문이다.


기준

그냥 Widget을 똑 떼어내어 사용할 곳에서 import 해오는 방식과는 무엇인가 달랐다. 그 이질감의 원인을 알아내고자 컴포넌트 분리의 기준을 찾아보았다.

이 UI가 독립적으로 재사용 가능한가?

이렇게 생각해도 확 와닿지 않았기에 코드로 살펴보겠다.

<div>
  <h2>할 일 목록</h2>
  <input />
  <button>추가</button>
</div>

이 코드를

function TodoInput() {
  return (
    <div>
      <input />
      <button>추가</button>
    </div>
  );
}

이렇게 하면 TodoInput을 불러와 다른 곳에서도 사용할 수 있다.
처음에 느꼈던 것과 비슷해서 반복인 느낌이 들었기에 더 찾아보니 나는 작성법에서 어려움을 느꼈던 것이었다.

고로 분리해서 필요한 곳에서 재사용한다는 것은 맞는 흐름이었고, 분리 과정에서 데이터 전달 방법과 같은 작성이 어려웠던 것이기에 이걸 다시 알아보고자 한다.


props

컴포넌트를 나누는 순간, 데이터의 흐름도 같이 설계해야 한다

먼저 분리하기 전 코드를 살펴보자.

function App() {
  const [title, setTitle] = useState("");

  const handleSubmit = (e) => {
    e.preventDefault();
    if (title.trim() === "") return;

    console.log(title);
    setTitle("");
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={title}
        onChange={(e) => setTitle(e.target.value)}
      />
      <button type="submit">추가</button>
    </form>
  );
}

여기서 TodoForm으로 입력 부분을 분리해보겠다.

function TodoForm() {
  const [title, setTitle] = useState("");

  const handleSubmit = (e) => {
    e.preventDefault();
    if (title.trim() === "") return;

    console.log(title);
    setTitle("");
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={title}
        onChange={(e) => setTitle(e.target.value)}
      />
      <button type="submit">추가</button>
    </form>
  );
}

이렇게 뚝 떼어내어서 가져오면 문제가 발생한다
할 일을 추가하는 로직은 부모인 App에 있어야하는데 자식인 TodoForm으로 들어가있다.

이렇게되면 상태 관리와 UI 역할이 섞이게 되어 문제가 된다

📌여러 컴포넌트에서 사용해야 하는 state는 부모에서 관리하는 것이 더 적절하기 때문이다


이걸 해결하기 위해 부모에 함수를 만들고

const handleAdd = (title) => {
  ...
};

이걸 props안 onAdd로 전달한다.

<TodoForm onAdd={handleAdd} />

그리고 자식에서 이 함수를 받아서 실행한다

function TodoForm({ onAdd }) {
  ...
  onAdd(title);
}

이렇게 해서 TodoForm의 코드는

import { useState } from 'react';

function TodoForm({ onAdd }) {
  const [title, setTitle] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    if (title.trim() === '') return;
    onAdd(title.trim());
    setTitle('');
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={title}
        onChange={(e) => setTitle(e.target.value)}
        placeholder="할일 입력..."
      />
      <button type="submit">
        추가
      </button>
    </form>
  );
}

이렇게 된다.


이제 추출을 해서 사용해보자

export / import

만든 TodoForm 마지막에

export default TodoForm;

export default를 넣어 외부에서 사용할 수 있게 만들고

App으로 돌아와서

import TodoForm from './components/TodoForm';

를 통해 import 해준다.

그리고

    <form onSubmit={handleSubmit}>
      <input
        value={title}
        onChange={(e) => setTitle(e.target.value)}
      />
      <button type="submit">추가</button>
    </form>

이 부분을

      {/* props로 함수 전달 */}
      <TodoForm onAdd={handleAdd} />

이렇게 변경해주면 깔끔하게 사용할 수 있다.


props vs state

마지막으로 둘의 차이를 비교해보자

📦 props

외부(부모)에서 전달받는 데이터

<TodoForm onAdd={handleAdd} />
function TodoForm({ onAdd }) {
  return <button onClick={onAdd}>추가</button>;
}
부모 → 자식으로 전달
읽기 전용 (수정 불가)
컴포넌트 입장에서는 “받아서 쓰는 값”

⚙️ state

컴포넌트 내부에서 관리하는 데이터

const [title, setTitle] = useState("");
컴포넌트 내부에서 생성
setState로 변경 가능
값이 바뀌면 UI가 다시 렌더링됨

🔥 가장 중요한 차이

👉 누가 데이터를 바꾸는가?

구분propsstate
소유자부모자기 자신
수정 가능❌ 불가능✅ 가능
역할전달관리
function App() {
  const [todos, setTodos] = useState([]);

  return <TodoForm onAdd={setTodos} />;
}
  • todos → state (App이 관리)

  • onAdd → props (TodoForm에게 전달)

  • 데이터는 App이 가지고 | 행동은 TodoForm이 실행


컴포넌트 분리에서 막혔던 작성법을 다시 공부해보았다. 컴포넌트 분리, 그리고 props가 조금은 이해되었다. 다음 응용에서는 잘해보자!

profile
다른 건 노력의 시간

1개의 댓글

comment-user-thumbnail
2026년 3월 25일

옛날에 컴포넌트를 너무 잘게 나눴다가 오히려 props 전달이 복잡해지고 유지보수가 어려워졌던 경험이 있어서, 적절한 기준으로 분리하는 게 중요하다는 말에 공감됐습니다!

답글 달기