리액트 강좌: 컴포넌트 트리에 데이터 공급하기 및 Context로 To Do 앱 리팩토링하기

리액트는 현대 웹 애플리케이션을 구축하는 데 있어 가장 인기 있는 라이브러리 중 하나입니다. 그 이유는 컴포넌트 기반의 아키텍처, 재사용성, 그리고 복잡한 UI를 쉽게 관리할 수 있는 방법 때문입니다. 이번 강좌에서는 리액트의 Context API를 활용하여 컴포넌트 트리에 데이터를 공급하는 방법과 이를 통해 간단한 To Do 애플리케이션을 리팩토링하는 방법에 대해 알아보겠습니다.

1. 리액트 기본 개념

리액트는 UI를 구성하는 컴포넌트의 집합입니다. 컴포넌트는 상태(state)와 속성(props)을 가지며, 이 둘을 통해 UI가 어떻게 렌더링될지를 결정합니다.

1.1 상태와 속성

상태는 컴포넌트의 내부 데이터를 나타내며, 사용자의 입력이나 네트워크 요청에 따라 변경될 수 있습니다. 반면, 속성은 부모 컴포넌트로부터 자식 컴포넌트로 전달되는 데이터입니다.

1.2 컴포넌트 트리

리액트 애플리케이션은 여러 개의 컴포넌트들로 구성된 트리 형태로 구성됩니다. 이 트리 구조를 통해 데이터가 부모에서 자식으로 흐르는 것을 관리하게 됩니다.

2. Context API 소개

Context API는 리액트에서 컴포넌트 트리의 깊은 곳에 있는 컴포넌트에 데이터를 전달하기 위해 사용됩니다. 여러 레벨을 거치지 않고도 데이터를 공급할 수 있도록 만들어져 있습니다. 이를 통해 속성(props) 전파의 복잡성을 줄일 수 있습니다.

2.1 Context 만들기

Context를 만들기 위해서는 먼저 React의 createContext 함수를 사용해야 합니다.

import React, { createContext, useContext, useState } from 'react';

const MyContext = createContext(); // Context 생성

2.2 Provider와 Consumer

Context의 Provider 컴포넌트를 사용하여 하위 컴포넌트로 데이터를 전달합니다. Provider는 모든 하위 컴포넌트에서 접근할 수 있는 데이터를 제공합니다.

<MyContext.Provider value={/* context value */}>
    {/* children */}
</MyContext.Provider>

2.3 Context 사용하기

하위 컴포넌트에서 Context를 사용하기 위해서는 useContext 훅을 사용하여 데이터를 가져옵니다.

const value = useContext(MyContext); // Context 사용

3. To Do 앱 구조 설정하기

이제 Context API를 활용하여 간단한 To Do 앱을 만들어 보겠습니다. 먼저 기본적인 컴포넌트 구조를 설정합니다.

3.1 기본 컴포넌트 구조

To Do 앱은 다음과 같은 컴포넌트로 구성됩니다.

  • App: 전체 앱을 감싸는 컴포넌트
  • TodoProvider: To Do 리스트의 상태를 관리하는 Provider 컴포넌트
  • TodoList: To Do 항목 리스트를 렌더링하는 컴포넌트
  • TodoItem: 개별 To Do 항목을 렌더링하는 컴포넌트
  • AddTodo: 새로운 To Do 항목을 추가하는 컴포넌트

3.2 앱 코드 작성하기

먼저, TodoProvider 컴포넌트를 작성하여 To Do 리스트의 상태를 관리합니다.

const TodoProvider = ({ children }) => {
    const [todos, setTodos] = useState([]);

    const addTodo = (todo) => {
        setTodos([...todos, todo]);
    };

    const removeTodo = (id) => {
        setTodos(todos.filter(todo => todo.id !== id));
    };

    return (
        <MyContext.Provider value={{ todos, addTodo, removeTodo }}>
            {children}
        </MyContext.Provider>
    );
};

그리고 TodoListAddTodo 컴포넌트를 각각 작성합니다.

const TodoList = () => {
    const { todos, removeTodo } = useContext(MyContext);

    return (
        <ul>
            {todos.map(todo => (
                <TodoItem key={todo.id} todo={todo} onRemove={removeTodo} />
            ))}</ul>
    );
};

const AddTodo = () => {
    const { addTodo } = useContext(MyContext);
    const [inputValue, setInputValue] = useState('');

    const handleSubmit = (e) => {
        e.preventDefault();
        if (!inputValue) return;
        
        addTodo({ id: Date.now(), text: inputValue });
        setInputValue('');
    };

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

3.3 최종 앱 구조

최종적으로 App 컴포넌트에서 TodoProvider를 사용하여 다른 컴포넌트를 감싸줍니다.

const App = () => {
    return (
        <TodoProvider>
            <h1>할 일 리스트</h1>
            <AddTodo />
            <TodoList />
        </TodoProvider>
    );
};

4. 상태 관리 및 리팩토링

상태 관리는 리액트 애플리케이션에서 중요한 부분입니다. 이를 통해 나중에 데이터의 흐름과 연관성을 더욱 명확하게 할 수 있습니다. Context API를 사용하면 하위 컴포넌트에서 상태를 직접 접근할 수 있어 더욱 효율적으로 관리할 수 있습니다.

4.1 상태 관리 패턴

상태 관리를 위해 여러 가지 패턴을 사용할 수 있습니다. Context API와 함께 사용하는 경우, 컴포넌트가 서브스크라이브 하도록 설계해야 합니다. 이를 통해 재렌더링을 최소화할 수 있습니다.

4.2 리팩토링 예시

리팩토링을 통해 추가적인 기능을 안전하게 구현할 수 있습니다. 예를 들어, To Do 항목의 완료 여부를 처리하는 로직을 추가해 보겠습니다.

const toggleTodo = (id) => {
    setTodos(todos.map(todo => todo.id === id ? { ...todo, completed: !todo.completed } : todo));
};

4.3 완료된 To Do 항목 시각화하기

완료된 To Do 항목을 시각적으로 나타내기 위해 TodoItem 컴포넌트를 수정합니다.

const TodoItem = ({ todo, onRemove }) => {
    return (
        <li style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
            {todo.text}
            <button onClick={() => onRemove(todo.id)}>삭제</button>
        </li>
    );
};

5. 결론

이번 강좌에서는 리액트의 Context API를 활용하여 컴포넌트 트리에 데이터를 공급하고 To Do 애플리케이션을 리팩토링하는 방법에 대해 배웠습니다. Context는 컴포넌트 간의 데이터 공유를 간편하게 하여 애플리케이션의 구조를 보다 깔끔하게 만들어 줍니다.

리액트를 활용한 상태 관리와 데이터 흐름의 이해는 앞으로 복잡한 애플리케이션을 구축하는 데 큰 도움이 될 것입니다. 이제 여러분도 직접 Context API를 활용하여 다양한 애플리케이션을 만들어 보시기를 권장합니다. 감사합니다!