본문 바로가기
Frontend/React

React : 컴포넌트 합성이란 ?

by 코딩쥐 2024. 8. 31.

컴포넌트 합성은 여러 개의 작은 컴포넌트를 조합하여 더 큰 기능을 수행하는 컴포넌트를 만드는 방법을 의미한다. 분리된 컴포넌트를 사용하는 쪽에서 조합해서 사용하는 방법이다. 컴포넌트 합성을 통해서 코드의 재사용성을 높이고 유지보수를 용이하게 할 수 있다. 컴포넌트 합성 시에 중요한 것은 단일 책임 원칙, 즉 각 컴포넌트는 하나의 기능만을 담당해야한다. 

 

컴포넌트 합성 예제

한 예시로 React로 TodoList를 구현한다고 했을 때,

  1. TodoInput이라는 컴포넌트는 등록을 하는 기능을 가지고 있고
  2. TodoItem은 각 Todo의 항목을 표시하고 수정 및 완료 버튼을 제공한다. 각 Todo항목을 관리하는 기능을 가지고 있다.
  3. TodoEdit는 TodoItem에서 Todo의 수정 기능을 가지고 있다. 
  4. TodoInput과 TodoItem을 조합하여 TodoApp 전체를 구성하게 된다. TodoApp에서 상태를 관리하여 Todo 항목을 추가하고 삭제 및 완료하는 기능을 제공한다.

아래는 해당 내용에 관련된 리액트 코드이다. 

 

1. TodoInput 컴포넌트 : 새로운 Todo 항목을 입력하고 해당 값을 전달하여 Todos에 추가하는 기능을 가지고 있다. 

- 새로 추가되는 todo의 text state를 가지고 있고, 추가하는 기능을 props로 가져와 핸들링 한다.

import { useState } from "react";

export default function TodoInput({handleAddTodo}){
    const [text, setText] = useState("");
    const handleOnClick = () => {
        handleAddTodo(text);
        setText("")
    }
    return(
        <div>
            <input type="text" value={text} onChange={(e) => setText(e.target.value)}/>
            <button onClick={handleOnClick}>추가</button>
        </div>
    )
}

 

2. TodoItem 컴포넌트 : 각 Todo 항목을 관리하는 컴포넌트로, 각 Todo와 관련된 기능(수정)은 해당 컴포넌트에 작성된다.

- TodoEdit의 부모 컴포넌트로, 수정과 관련된 state와 기능을 해당 컴포넌트에 작성하여 TodoEdit 컴포넌트에 props로 전달한다.

- Todos와 관련된 삭제 / 완료 / 수정과 관련된 기능을 부모 컴포넌트로 받아 해당 컴포넌트에서 핸들링 한다. 

import { useState } from "react";
import TodoEdit from "./TodoEdit";

export default function TodoItem({ todo, index, handleDeleteTodo, handleCompleteTodo, handleEditTodo }) {
    // 수정하는 내용에 대한 state를 갖는다.
    const [isEditable, setIsEditable] = useState(false);

    // 수정버튼을 클릭했을 시에 isEditable true로 변경
    const toggleEdit = () => {
        setIsEditable(true);
    }

    const handleEditSubmit = (editedText) => {
        handleEditTodo(index, editedText);
        setIsEditable(false);
    };

    return (
        <div>
            {/* isEditable이 true일 경우에는 TodoEdit 컴포넌트 호출*/}

            {isEditable ?
                <TodoEdit text={todo.text} onEditSubmit={handleEditSubmit} />
                :
                <>
                    {/* todo객체에 isCompleted가 true일 경우에는 중간줄 표시 */}
                    < h2 style={{ textDecoration: todo.isCompleted ? "line-through" : "none" }}>{todo.text}</h2>
                    <button onClick={toggleEdit}>수정</button>

                    {/* 완료버튼 클릭시에 completedTodo가 실행되어 todo 객체 안에 isCompleted가 true로 변경 */}
                    <button onClick={() => handleCompleteTodo(index)}>완료</button>

                    {/* 삭제버튼 클릭시에 deleteTodo가 실행되어 filter를 통해 해당 인덱스 삭제 */}
                    <button onClick={() => handleDeleteTodo(index)}>삭제</button>
                </>
            }

        </div >
    )
}

 

3. TodoEdit 컴포넌트 : TodoItem의 하위 컴포넌트로, 각 Todo의 수정과 관련된 기능은 해당 컴포넌트에 작성된다. 

- 상위 컴포넌트에서 todo의 text와 수정과 관련된 기능을 props로 받아 수정 후에 function의 매개변수를 통해 전달한다.

import { useState } from "react";

export default function TodoEdit({ text, onEditSubmit }) {
    const [editedText, setEditedText] = useState(text);

    return (
        <div>
            <input
                type="text"
                value={editedText}
                onChange={(e) => setEditedText(e.target.value)}
            />
            <button onClick={() => onEditSubmit(editedText)}>수정완료</button>
        </div>
    );
}

 

4. TodoApp 컴포넌트 : 전체적인 Todos를 관리하는 컴포넌트로 Todos와 관련된 기능(추가, 삭제, 완료, 수정)들은 해당 컴포넌트에서 작성되어 하위 컴포넌트로 props를 통해 전달된다.

import { useState } from 'react';
import './App.css';
import TodoItem from './components/TodoItem';
import TodoInput from './components/TodoInput';

function App() {
  // App에서는 Todo의 집합(todo의 내용과 수정여부)에 대한 state와, 각 Todo의 내용에 대한 state를 갖는다.
  const [todos, setTodos] = useState([]);

  // App에서는 Todo 항목을 추가 / 삭제 / 완료의 기능을 제공한다. 
  // 추가 
  const addTodo = (todoText) => {
    setTodos([...todos, { text: todoText, isCompleted: false, isEditable: false}]);
  }

  //삭제
  const deleteTodo = (index) => {
    // todos의 각 아이템들의 인텍스와 비교해서, 인덱스가 다른 아이템들을 모아 새로운 배열로 생성한다. 
    setTodos(todos.filter((_, todoIdx) => todoIdx !== index));
  }

  //완료
  const completeTodo = (index) => {
    const newTodo = [...todos];
    newTodo[index].isCompleted = true;
    setTodos(newTodo);
  }

  // 수정
  const editTodo = (index, newText) => {
    const newTodos = [...todos];
    newTodos[index].text = newText;
    setTodos(newTodos);
  };

  return (
    <>
      {/* TodoInput에 보낼 props : addTodo */}
      <TodoInput handleAddTodo={addTodo} />
      {/* TodoItem에 보낼 props는 각 todo와 index, deleteTodo, completeTodo, editTodo  */}
      {todos.map((todo, index) => (
        <TodoItem
          key={index}
          todo={todo}
          index={index}
          handleDeleteTodo={deleteTodo}
          handleCompleteTodo={completeTodo}
          handleEditTodo={editTodo}/>
      ))}
    </>
  );
}

export default App;