리액트 강좌: To Do 앱 useReducer 사용 앱 업그레이드

리액트는 현대 웹 개발의 중요한 도구로 자리잡았습니다. 특히, 상태 관리에 있어 다양한 방법이 있지만, 그 중 useReducer는 더 복잡한 상태 로직을 효과적으로 관리할 수 있도록 돕는 React 훅입니다. 본 강좌에서는 기본적인 To Do 앱을 사용하여 useReducer를 활용하는 방법을 구체적으로 다루겠습니다.

1. 프로젝트 구조

우선, 프로젝트의 기본 구조를 살펴보겠습니다. 일반적으로 To Do 앱의 구조는 다음과 같이 구성됩니다:


/to-do-app
|-- /src
|   |-- /components
|   |   |-- TodoItem.js
|   |   |-- TodoList.js
|   |   └-- TodoForm.js
|   |
|   |-- App.js
|   └-- index.js
|-- package.json
|-- README.md
        

위 구조에서 TodoItem은 개별 할 일 항목을 나타내고, TodoList는 모든 할 일 목록을 보여줍니다. TodoForm을 사용하여 새로운 할 일을 추가합니다.

2. useReducer 소개

useReducer는 복잡한 상태를 다룰 때 유용하며, 다음과 같은 형태로 사용할 수 있습니다:


const [state, dispatch] = useReducer(reducer, initialState);
        

state: 현재 상태를 나타내며, dispatch: 상태 업데이트를 위한 함수입니다. reducer는 상태를 어떻게 변화시킬지를 정의하는 함수입니다.

3. 기본 To Do 앱 구현

먼저 기본 To Do 앱을 구현해 보겠습니다. 아래 코드는 간단한 리액트 앱으로, 사용자가 할 일을 추가하고 목록을 보여주는 기능을 포함합니다.


import React, { useState } from 'react';

const App = () => {
    const [todos, setTodos] = useState([]);
    const [input, setInput] = useState('');

    const addTodo = () => {
        if (input.trim() !== '') {
            setTodos([...todos, { text: input, completed: false }]);
            setInput('');
        }
    };

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

    return (
        

나의 To Do 리스트

setInput(e.target.value)} placeholder="할 일을 입력하세요" />
    {todos.map((todo, index) => (
  • toggleTodo(index)} style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}> {todo.text}
  • ))}
); }; export default App;

4. useReducer로 상태 관리하기

이제 위의 코드를 useReducer로 개선해보겠습니다. 상태를 복잡하게 관리할 필요가 있을 때, useReducer가 더 유용할 수 있습니다.


import React, { useReducer } from 'react';

const initialState = { todos: [] };

const reducer = (state, action) => {
    switch (action.type) {
        case 'ADD_TODO':
            return { 
                ...state, 
                todos: [...state.todos, { text: action.payload, completed: false }] 
            };
        case 'TOGGLE_TODO':
            return {
                ...state,
                todos: state.todos.map((todo, index) =>
                    index === action.payload ? { ...todo, completed: !todo.completed } : todo
                ),
            };
        default:
            return state;
    }
};

const App = () => {
    const [state, dispatch] = useReducer(reducer, initialState);
    const [input, setInput] = React.useState('');

    const addTodo = () => {
        if (input.trim() !== '') {
            dispatch({ type: 'ADD_TODO', payload: input });
            setInput('');
        }
    };

    return (
        

나의 To Do 리스트 (useReducer)

setInput(e.target.value)} placeholder="할 일을 입력하세요" />
    {state.todos.map((todo, index) => (
  • dispatch({ type: 'TOGGLE_TODO', payload: index })} style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}> {todo.text}
  • ))}
); }; export default App;

5. useReducer의 장점

useReducer를 사용하면, 다음과 같은 장점이 있습니다:

  • 복잡한 상태 로직: 여러 상태 전환을 독립적인 행동으로 관리할 수 있어 복잡한 상태 로직을 명확하게 유지할 수 있습니다.
  • 중앙 집중식 관리: 여러 상태 업데이트가 있을 때, 하나의 reducer 함수로 모든 상태 변경을 처리할 수 있습니다.
  • 성능 향상: 불변성 유지가 용이하여 성능 최적화에 도움이 됩니다.

6. 컴포넌트 분리

이제 App 컴포넌트를 더 작은 컴포넌트로 나누어 코드의 가독성을 높여 보겠습니다. TodoList와 TodoForm 컴포넌트를 만들어보겠습니다.


const TodoList = ({ todos, toggleTodo }) => (
    
    {todos.map((todo, index) => (
  • toggleTodo(index)} style={{ textDecoration: todo.completed ? 'line-through' : 'none' }} > {todo.text}
  • ))}
); const TodoForm = ({ addTodo, input, setInput }) => (
setInput(e.target.value)} placeholder="할 일을 입력하세요" />
);

7. 최종 코드


import React, { useReducer, useState } from 'react';

const initialState = { todos: [] };

const reducer = (state, action) => {
    switch (action.type) {
        case 'ADD_TODO':
            return { 
                ...state, 
                todos: [...state.todos, { text: action.payload, completed: false }] 
            };
        case 'TOGGLE_TODO':
            return {
                ...state,
                todos: state.todos.map((todo, index) =>
                    index === action.payload ? { ...todo, completed: !todo.completed } : todo
                ),
            };
        default:
            return state;
    }
};

const TodoList = ({ todos, toggleTodo }) => (
    
    {todos.map((todo, index) => (
  • toggleTodo(index)} style={{ textDecoration: todo.completed ? 'line-through' : 'none' }} > {todo.text}
  • ))}
); const TodoForm = ({ addTodo, input, setInput }) => (
setInput(e.target.value)} placeholder="할 일을 입력하세요" />
); const App = () => { const [state, dispatch] = useReducer(reducer, initialState); const [input, setInput] = useState(''); const addTodo = () => { if (input.trim() !== '') { dispatch({ type: 'ADD_TODO', payload: input }); setInput(''); } }; return (

나의 To Do 리스트 (useReducer)

dispatch({ type: 'TOGGLE_TODO', payload: index })} />
); }; export default App;

8. 마치며

이번 강좌에서는 To Do 앱을 통해 useReducer를 적용하는 방법을 알아보았습니다. useReducer는 복잡한 상태 로직을 간결하게 관리할 수 있도록 도와주며, 가독성과 유지보수성 또한 높입니다. 더 나아가 여러 컴포넌트로 구성하여 코드의 구조를 개선할 수 있었습니다. 이러한 패턴은 더욱 복잡한 애플리케이션에서도 유용하게 사용될 수 있습니다. 다음 시간에는 useContext를 활용하여 전역 상태 관리에 대해 알아보겠습니다.

리액트 강좌: useReducer 이해하기

React는 구성 요소를 기반으로 한 JavaScript 라이브러리로, 사용자 인터페이스(UI) 구축을 위해 널리 사용됩니다. React의 두 가지 주요 상태 관리 훅은 useState와 useReducer입니다. 이번 강좌에서는 useReducer에 대해 깊이 있게 살펴보겠습니다.

1. useReducer란?

useReducer는 리액트의 내장 훅 중 하나로, 복잡한 상태 로직을 관리하기 위해 사용됩니다. 이 훅은 상태 변화가 있을 때마다 특정한 함수를 호출하여 상태를 업데이트합니다. useState가 간단한 상태 관리를 위한 훅이라면, useReducer는 보다 복잡한 로직이나 다수의 상태 변화를 필요로 할 때 적합합니다.

2. useReducer 기본 사용법

useReducer를 사용하는 기본적인 방법은 다음과 같습니다.


import React, { useReducer } from 'react';

const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
    </div>
  );
}

위의 예제에서 볼 수 있듯이 useReducer는 두 개의 인자를 사용합니다:
reducer 함수초기 상태(initial state). reducer 함수는 현재 상태와 액션을 매개변수로 받아서 새로운 상태를 반환합니다. dispatch 함수는 액션 객체를 사용하여 상태를 업데이트합니다.

3. useReducer의 장점

  • 복잡한 상태 관리: useReducer는 여러 상태를 관리하기 위한 강력한 도구입니다. 여러 액션과 상태를 명확히 규명할 수 있습니다.
  • 리팩토링과 확장성: reducer 함수는 독립적으로 관리되기 때문에 상태 로직을 리팩토링하기가 용이합니다.
  • 테스트 용이성: reducer 함수는 순수 함수이기 때문에, 이를 단위 테스트하기가 상대적으로 수월합니다.

4. useReducer와 useState 비교

useState와 useReducer는 상태를 관리하는 데 도움을 주지만, 사용 목적에 따라 각각의 적합한 상황이 다릅니다.

특징 useState useReducer
단순한 상태 유리 불리
복잡한 상태 불리 유리
상태 변경 로직 불리 유리
상태의 분리 불리 유리

5. 실전 예제: ToDo 리스트 앱 만들기

이제 useReducer를 활용하여 간단한 ToDo 리스트 앱을 만들어 보겠습니다. 이 앱은 할 일을 추가하고, 삭제할 수 있는 기능을 포함합니다.


import React, { useReducer } from 'react';

const initialState = { todos: [] };

function reducer(state, action) {
  switch (action.type) {
    case 'add':
      return { ...state, todos: [...state.todos, action.payload] };
    case 'remove':
      return { ...state, todos: state.todos.filter((_, index) => index !== action.payload) };
    default:
      throw new Error();
  }
}

function TodoApp() {
  const [state, dispatch] = useReducer(reducer, initialState);
  const [input, setInput] = React.useState('');

  const addTodo = () => {
    dispatch({ type: 'add', payload: input });
    setInput('');
  };

  return (
    <div>
      <h2>ToDo List</h2>
      <input 
        type="text" 
        value={input} 
        onChange={(e) => setInput(e.target.value)} 
      />
      <button onClick={addTodo}>Add</button>
      <ul>
        {state.todos.map((todo, index) => (
          <li key={index}>
            {todo} <button onClick={() => dispatch({ type: 'remove', payload: index })}>Remove</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

위의 TodoApp 컴포넌트는 사용자가 할 일을 추가하고 제거할 수 있는 기능을 제공합니다. useReducer를 사용하여 상태를 관리하며, 각 액션(type)별로 할 일을 추가하거나 삭제하는 로직을 정의하고 있습니다.

6. useReducer의 고급 사용법

useReducer는 단순한 상태 관리를 넘어서, 고급 기능을 지원합니다. 다음은 그 일부입니다:

6.1 초기 상태 로딩

초기 상태를 비동기적으로 로드할 수 있는 방법입니다. 이를 위해 useEffect 훅을 사용합니다.


import React, { useReducer, useEffect } from 'react';

function App() {
  const [state, dispatch] = useReducer(reducer, initialState);

  useEffect(() => {
    const fetchData = async () => {
      const response = await fetch('/api/todos');
      const data = await response.json();
      data.forEach(todo => dispatch({ type: 'add', payload: todo }));
    };
    fetchData();
  }, []);

  // 나머지 앱 코드...
}

6.2 중첩된 상태 관리

여러 개의 reducer를 조합하여 구조화된 상태 관리를 적용할 수 있습니다. context API와 함께 사용할 경우, 전역 상태 관리를 손쉽게 할 수 있습니다.


function mainReducer(state, action) {
  return {
    counter: counterReducer(state.counter, action),
    todos: todoReducer(state.todos, action),
  };
}

// 상태와 dispatch는 여러 개의 reducer로 제공됩니다.
const [state, dispatch] = useReducer(mainReducer, initialState);

7. 결론

useReducer는 리액트에서 복잡한 상태 관리 로직을 구축할 수 있는 강력한 도구입니다. 특히 여러 상태 변화가 필요한 경우, useReducer를 통해 코드의 가독성과 유지보수성을 높일 수 있습니다. 이번 강좌에서 우리가 배운 내용을 바탕으로 여러분만의 리액트 애플리케이션에서 useReducer를 활용해 보시기 바랍니다.

추가로 실습하고 싶은 예제나 질문이 있다면 댓글로 남겨주세요. 여러분의 리액트 개발 여정을 응원합니다!

리액트 강좌: Truthy & Falsy

작성자: 조광형

날짜: 2024년 11월 26일

1. 서론

자바스크립트는 동적 타입을 지원하는 언어로, 값이 가진 “진리값(truthiness)”과 “거짓값(falsiness)”에 따라 흐름이 크게 달라집니다. 이 강좌에서는 리액트에서 어떻게 truthy와 falsy 값을 활용하는지에 대해 알아보겠습니다. 기초적인 개념부터 고급 기술까지 다룰 예정이니, 주의 깊게 읽어보시기 바랍니다.

2. Truthy와 Falsy란?

자바스크립트에서 값은 항상 Boolean 값으로 변환될 수 있습니다. 이때, 특정 값은 ‘truthy’로 평가되고, 나머지는 ‘falsy’로 평가됩니다.

2.1 Falsy 값의 정의

자바스크립트에서 ‘falsy’로 평가되는 값은 다음과 같습니다:

  • false
  • 0 (숫자)
  • “” (빈 문자열)
  • null
  • undefined
  • NaN (Not-a-Number)

2.2 Truthy 값의 정의

위의 ‘falsy’ 값 이외의 모든 값은 truthy로 평가됩니다. 여기에는 다음과 같은 값들이 포함됩니다:

  • true
  • 1 (숫자)
  • “hello” (문자열)
  • { } (빈 객체)
  • [ ] (빈 배열)
  • 함수

3. Truthy & Falsy의 활용

리액트에서 컴포넌트의 상태를 관리하거나 조건부 렌더링을 할 때 truthy와 falsy 값을 자주 사용합니다. 따라서 이러한 개념에 대한 이해는 리액트 애플리케이션을 개발하는 데 매우 중요합니다.

3.1 조건부 렌더링

조건부 렌더링은 상태에 따라 어떤 컴포넌트를 렌더링할 것인지 결정하는 방식입니다. 아래의 코드를 살펴보세요.


{isLoggedIn ?  : }
            

위 코드에서 isLoggedIn이 truthy한 경우 LogoutButton 컴포넌트를 렌더링하고, 그렇지 않은 경우에는 LoginButton을 렌더링합니다.

3.2 논리 AND (&&) 연산자

리액트에서는 논리 AND 연산자를 사용하여 조건부 렌더링을 쉽게 할 수 있습니다. 예를 들어:


{isLoggedIn && }
            

이 코드는 isLoggedIn이 truthy한 경우에만 WelcomeMessage를 렌더링합니다.

4. Truthy & Falsy 값의 테스트

4.1 콘솔에서의 확인

자바스크립트의 콘솔에서 다양한 값이 truthy 또는 falsy인지 확인할 수 있습니다.


console.log(Boolean(false)); // false
console.log(Boolean(0)); // false
console.log(Boolean("")); // false
console.log(Boolean(null)); // false
console.log(Boolean(undefined)); // false
console.log(Boolean(NaN)); // false

console.log(Boolean(true)); // true
console.log(Boolean(1)); // true
console.log(Boolean("hello")); // true
console.log(Boolean({})); // true
console.log(Boolean([])); // true
console.log(Boolean(function() {})); // true
            

위 코드를 실행해보면 각 값에 대한 진리값을 확인할 수 있습니다.

5. 고급 사용법

리액트에서 truthy와 falsy 값을 좀 더 고급적으로 사용하는 몇 가지 패턴에 대해 알아보겠습니다.

5.1 사용자 정의 훅

리액트에서 사용자 정의 훅을 활용하여 truthy와 falsy 값을 활용하는 예를 살펴보겠습니다.


import { useState } from 'react';

function useLogin() {
    const [isLoggedIn, setLoggedIn] = useState(false);
    
    const login = () => {
        setLoggedIn(true);
    };

    const logout = () => {
        setLoggedIn(false);
    };

    return { isLoggedIn, login, logout };
}
            

위의 사용자 정의 훅을 통해 로그인 상태를 관리할 수 있습니다.

5.2 컨텍스트 API

리액트의 Context API를 사용하여 전체 애플리케이션에서 login 상태를 공유할 수 있습니다. 아래와 같이 설정합니다:


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

const AuthContext = createContext();

export function AuthProvider({ children }) {
    const { isLoggedIn, login, logout } = useLogin();
    return (
        
            {children}
        
    );
}

export function useAuth() {
    return useContext(AuthContext);
}
            

이제 애플리케이션의 어느 위치에서나 useAuth 훅을 통해 로그인 상태와 관련된 값을 사용할 수 있습니다.

6. 결론

리액트에서 truthy와 falsy 개념은 컴포넌트 렌더링의 조건을 설정할 때 중요한 역할을 합니다. 이러한 값을 어떻게 활용하는지는 애플리케이션의 동작 방식에 큰 영향을 미칠 수 있습니다. 본 강좌를 통해 기본적인 이해를 마친 후, 이를 실제 애플리케이션에서 적용해 보시기를 바랍니다.

이 외에도 다양한 리액트 주제에 관해 알고 싶다면 댓글을 남겨 주세요. 감사합니다!

리액트 강좌: useEffect에 대한 모든 것

리액트는 현대 웹 애플리케이션을 구축하기 위한 가장 인기 있는 JavaScript 라이브러리 중 하나입니다. 그중에서도 useEffect 훅은 컴포넌트의 생명 주기를 제어하고, 사이드 이펙트를 관리하는 데 매우 중요한 역할을 합니다. 이 글에서는 useEffect의 기본 개념, 사용법, 다양한 사례를 상세히 다루겠습니다.

1. useEffect란 무엇인가?

useEffect는 React 라이브러리에서 제공하는 훅 중 하나로, 함수형 컴포넌트에서 사이드 이펙트를 발생시키기 위해 사용됩니다. 사이드 이펙트란 데이터 가져오기, 수동 DOM 조작, 타이머 설정 또는 다른 외부 시스템과의 상호작용 등을 포함합니다.

리액트의 컴포넌트는 상태(state)와 속성(props)만을 작성하도록 설계되었지만, 때때로 컴포넌트가 렌더링될 때 다른 작업을 수행해야 할 필요가 있습니다. 이러한 작업을 수행하기 위해 useEffect 훅을 사용합니다.

2. useEffect의 기본 사용법

useEffect는 다음과 같은 구문으로 사용할 수 있습니다:

useEffect(() => {
  // 사이드 이펙트 코드
}, [의존성 배열]);

위 구문에서 첫 번째 매개변수는 사이드 이펙트를 수행하는 함수이며, 두 번째 매개변수는 의존성 배열입니다. 의존성 배열에는 해당 훅이 의존하는 상태나 props가 들어갑니다.

2.1. 간단한 예제

import React, { useEffect, useState } from 'react';

function ExampleComponent() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = \`Count: \${count}\`;
  }, [count]);

  return (
    

현재 카운트: {count}

); }

위 예제에서는 count가 변경될 때마다 문서 제목이 업데이트됩니다. count가 의존성 배열에 포함되어 있기 때문에, setCount 함수가 호출되어 count가 업데이트될 경우에만 사이드 이펙트가 실행됩니다.

3. useEffect의 실행 타이밍

useEffect는 컴포넌트가 렌더링된 후에 실행됩니다. 정리하자면 다음과 같습니다:

  • 컴포넌트가 처음 렌더링될 때 유효성 검사 후 실행
  • 의존성 배열에 명시된 값이 변경될 때 다시 실행

예를 들어, 의존성 배열이 비어 있는 경우:

useEffect(() => {
  // 최초 렌더링 후에만 실행
}, []);

3.1. 클린업 함수

useEffect는 클린업 함수를 반환할 수 있습니다. 이 클린업 함수는 컴포넌트가 언마운트될 때 또는 다음 효과가 실행되기 전에 호출됩니다. 이는 메모리 누수를 방지하는 데 매우 유용합니다.

useEffect(() => {
  const timer = setTimeout(() => {
    console.log('타이머 실행!');
  }, 1000);

  return () => {
    clearTimeout(timer); // 컴포넌트 언마운트 시 타이머 클리어
  };
}, []);

4. useEffect에 대한 고급 사용법

이제 기본 사용법을 익혔으니, useEffect의 고급 개념으로 넘어가 보겠습니다. 개체 관리를 위한 세부 매개변수 전달, 조건부 실행 등의 방법을 알아보겠습니다.

4.1. 조건부 실행

때때로 특정 조건이 충족될 때만 useEffect를 실행하고 싶을 수 있습니다. 조건문을 사용해 이를 구현할 수 있습니다:

useEffect(() => {
  if (count > 0) {
    console.log('Count is positive');
  }
}, [count]);

4.2. 여러 개의 useEffect 사용하기

하나의 컴포넌트에서 여러 개의 useEffect를 사용하여 각 효과를 독립적으로 관리할 수 있습니다. 각 효과는 자체 의존성 배열을 가질 수 있습니다.

useEffect(() => {
  // 데이터 가져오기
}, [url]);

useEffect(() => {
  // 클리어 타이머
  return () => {
    clearInterval(timerId);
  };
}, []);

5. useEffect 사례 연구

이제 실제 사용 사례를 통해 useEffect의 유용성을 살펴보겠습니다.

5.1. API 데이터 fetching

리액트 애플리케이션에서 API 데이터를 fetching하는 가장 일반적인 경우에서 useEffect를 사용합니다:

import React, { useEffect, useState } from 'react';

function DataFetchingComponent() {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch('https://api.example.com/data')
      .then((response) => response.json())
      .then((data) => {
        setData(data);
        setLoading(false);
      });
  }, []);

  if (loading) return 

로딩 중...

; return (
    {data.map(item =>
  • {item.name}
  • )}
); }

5.2. 실시간 데이터 업데이트

WebSocket이나 서버 전송 이벤트(SSE)를 통해 실시간 데이터를 업데이트할 때 useEffect를 사용할 수 있습니다:

import React, { useEffect, useState } from 'react';

function RealTimeComponent() {
  const [messages, setMessages] = useState([]);

  useEffect(() => {
    const socket = new WebSocket('ws://your-websocket-url');

    socket.onmessage = (event) => {
      const newMessage = JSON.parse(event.data);
      setMessages(prevMessages => [...prevMessages, newMessage]);
    };

    return () => {
      socket.close();
    };
  }, []);

  return (
    
{messages.map((msg, index) => (
{msg.content}
))}
); }

6. useEffect vs 클래스 컴포넌트의 생명주기 메소드

함수형 컴포넌트의 useEffect는 클래스 컴포넌트에서 사용하는 생명주기 메소드(componentDidMount, componentDidUpdate, componentWillUnmount)의 역할을 수행합니다.

예를 들어, componentDidMountcomponentWillUnmount를 구현할 수 있는 useEffect의 사용은 다음과 같습니다:

useEffect(() => {
  // 컴포넌트가 마운트될 때 실행
  fetchData();

  return () => {
    // 컴포넌트가 언마운트될 때 실행
    cleanup();
  };
}, []);

7. 최적화 및 성능 고려 사항

useEffect를 사용할 때는 성능에 미치는 영향을 고려해야 합니다. 다음은 성능을 최적화하기 위한 몇 가지 팁입니다:

  • 의존성 배열을 올바르게 설정하세요. 불필요한 재렌더링을 방지할 수 있습니다.
  • 예를 들어, 특정 상태가 변경될 때만 효과를 실행해야 한다면 이를 명확히 하십시오.
  • 지속하는 메모리 리소스를 해제하기 위해 클린업 함수를 적절히 사용하십시오.

8. 자주 묻는 질문(FAQ)

8.1. useEffect는 언제 호출되나요?

useEffect는 컴포넌트가 마운트되거나 의존성이 업데이트될 때 호출됩니다.

8.2. useEffect를 여러 번 사용할 수 있나요?

네, 한 컴포넌트에서 여러 개의 useEffect를 사용할 수 있습니다. 각 useEffect는 독립적이며, 다른 역활을 수행할 수 있습니다.

8.3. useEffect의 의존성 배열을 비워두면 어떻게 되나요?

의존성 배열을 빈 배열로 설정하면, 컴포넌트가 처음 마운트될 때만 실행되고 이후에는 실행되지 않습니다.

8.4. useEffect 내부에서 setState를 호출해도 괜찮나요?

예, 상태를 업데이트하기 위해 setState를 호출할 수 있으며, 이로 인해 컴포넌트가 재렌더링됩니다. 다만, 무한 루프를 피하기 위해 적절히 의존성을 관리해 주세요.

9. 마치며

이번 강좌에서는 리액트의 useEffect 훅에 대해 자세히 알아보았습니다. useEffect는 사이드 이펙트를 관리하는 데 필수적인 도구로, 리액트 애플리케이션에서 흔히 사용됩니다. 처음에는 어렵게 느껴질 수 있지만, 계속해서 사용하다 보면 자연스럽게 익숙해질 것입니다.

이 글을 통해 useEffect의 강력한 기능과 활용 방법에 대해 이해하고, 자신의 프로젝트에 실제로 적용해 보시길 바랍니다. 앞서 언급한 예제와 같은 다양한 사용 사례를 실습하면서 추가적인 경험을 쌓아보세요!

리액트 강좌: To Do 앱 예제, 기능 구현 준비하기

안녕하세요! 이번 강좌에서는 React를 사용하여 To Do 앱을 만드는 방법에 대해 알아보겠습니다. React는 사용자 인터페이스를 구축하기 위한 라이브러리로, 특히 컴포넌트 기반의 아키텍처로 인해 개발자가 재사용 가능한 UI 구성 요소를 만들 수 있게 해줍니다. To Do 앱은 리액트를 처음 배우는 사람에게 적합한 프로젝트로, 기본적인 CRUD(Create, Read, Update, Delete) 작업을 이해하는 데 도움을 줍니다.

1. 프로젝트 개요

이번 프로젝트에서는 사용자가 할 일을 추가하고, 삭제하며, 완료 여부를 체크할 수 있는 간단한 To Do 앱을 만들어 보겠습니다. 이 앱은 React의 상태 관리 및 이벤트 처리의 기초를 이해하는 데 유용합니다.

2. 필요한 도구 및 라이브러리

  • Node.js: Javascript 런타임 환경. React 프로젝트를 설정하고 실행하기 위해 필요합니다.
  • npm: Node.js 패키지 매니저로, 필요한 라이브러리를 설치하는 데 사용됩니다.
  • React: 사용자 인터페이스를 구축하기 위한 JavaScript 라이브러리입니다.
  • Visual Studio Code: 코드 편집기로, 개발 환경을 설정하는 데 사용됩니다.

3. 환경 설정

우선, Node.js가 설치되어 있는지 확인하세요. 설치되어 있지 않다면, Node.js 공식 웹사이트에서 다운로드하여 설치합니다.

설치가 완료되면 다음 명령어를 이용해 새로운 React 앱을 생성합니다:

npx create-react-app todo-app

앱 생성이 완료되면, 생성된 디렉터리로 이동하여 앱을 실행합니다:

cd todo-app
npm start

브라우저에서 http://localhost:3000을 열면 기본 React 앱이 표시될 것입니다.

4. 프로젝트 구조 이해하기

프로젝트가 생성되면 다음과 같은 폴더 구조를 확인할 수 있습니다:


todo-app/
├── node_modules/
├── public/
└── src/
    ├── App.css
    ├── App.js
    ├── index.css
    ├── index.js
    └── logo.svg

여기서 주로 수정할 파일은 src/App.js입니다. 이 파일의 내용을 수정하여 To Do 앱의 기능을 구현해 나갈 것입니다.

5. 컴포넌트 설계

To Do 앱은 다음과 같은 컴포넌트로 구성될 것입니다:

  • ToDoApp: 앱의 주요 컴포넌트로 모든 서브 컴포넌트를 포함합니다.
  • ToDoList: 추가된 할 일 목록을 표시합니다.
  • ToDoItem: 개별 할 일을 나타내며, 완료 및 삭제 기능을 지원합니다.
  • AddToDo: 새로운 할 일을 추가하는 폼을 제공합니다.

5.1 ToDoApp 컴포넌트 설정

src/App.js 파일을 열고 다음과 같이 내용을 수정합니다:

import React, { useState } from 'react';
import ToDoList from './ToDoList';
import AddToDo from './AddToDo';
import './App.css';

const ToDoApp = () => {
    const [todos, setTodos] = useState([]);

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

    const deleteTodo = (index) => {
        const newTodos = todos.filter((_, i) => i !== index);
        setTodos(newTodos);
    };

    const toggleTodo = (index) => {
        const newTodos = [...todos];
        newTodos[index].completed = !newTodos[index].completed;
        setTodos(newTodos);
    };

    return (
        

To Do List

); }; export default ToDoApp;

5.2 ToDoList 컴포넌트 생성

할 일 목록 컴포넌트를 생성하기 위해 src/ToDoList.js 파일을 생성하고 다음 내용을 추가합니다:

import React from 'react';
import ToDoItem from './ToDoItem';

const ToDoList = ({ todos, deleteTodo, toggleTodo }) => {
    return (
        
    {todos.map((todo, index) => ( ))}
); }; export default ToDoList;

5.3 ToDoItem 컴포넌트 생성

개별 할 일 항목을 표시하는 컴포넌트를 만들기 위해 src/ToDoItem.js 파일을 생성하고 아래와 같이 작성합니다:

import React from 'react';

const ToDoItem = ({ todo, deleteTodo, toggleTodo, index }) => {
    return (
        
  • toggleTodo(index)}>{todo.text}
  • ); }; export default ToDoItem;

    5.4 AddToDo 컴포넌트 생성

    새로운 할 일을 추가하는 폼을 생성하기 위해 src/AddToDo.js 파일을 생성하고 다음 코드를 추가합니다:

    import React, { useState } from 'react';
    
    const AddToDo = ({ addTodo }) => {
        const [inputValue, setInputValue] = useState('');
    
        const handleSubmit = (e) => {
            e.preventDefault();
            if (inputValue.trim()) {
                addTodo({ text: inputValue.trim(), completed: false });
                setInputValue('');
            }
        };
    
        return (
            
    setInputValue(e.target.value)} placeholder="할 일을 입력하세요" />
    ); }; export default AddToDo;

    6. 스타일링

    이제 기본적인 기능 구현이 끝났으니, 앱을 조금 더 예쁘게 만들기 위해 스타일링을 추가해보겠습니다. src/App.css 파일을 수정하여 다음과 같이 스타일을 추가합니다:

    .todo-app {
        max-width: 500px;
        margin: 0 auto;
        padding: 20px;
        border: 1px solid #ccc;
        border-radius: 5px;
        box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
    }
    
    h1 {
        text-align: center;
    }
    
    form {
        display: flex;
        justify-content: space-between;
    }
    
    form input {
        flex: 1;
        margin-right: 10px;
    }
    
    ul {
        list-style-type: none;
        padding: 0;
    }
    
    li {
        display: flex;
        justify-content: space-between;
        padding: 10px 0;
    }
    
    .completed {
        text-decoration: line-through;
        color: gray;
    }

    7. 앱 실행 및 테스트

    모든 컴포넌트와 스타일이 준비되었으므로, 앱을 실행해 보세요. 브라우저에서 http://localhost:3000을 열고 할 일을 추가하고, 삭제 및 완료 상태를 변경하는 기능이 제대로 작동하는지 확인합니다.

    8. 마무리 및 추가 기능

    이제 기본적인 To Do 앱이 완성되었습니다. 하지만 리액트를 더 잘 활용하기 위해 추가 기능을 고려해볼 수 있습니다. 예를 들어:

    • 할 일 리스트를 로컬 스토리지에 저장하여 새로고침 후에도 유지하기
    • 할 일을 편집하는 기능 추가하기
    • 필터 기능 추가하여 완료된 할 일만 보기

    이번 강좌에서 배운 내용을 바탕으로 여러분의 아이디어를 적용해보세요! 감사합니다!