본문 바로가기

React

[React] To-Do 리스트 만들기

이번 글에서는 React의 기본기를 천천히 익히면서 많이 쓰이는 To-Do 리스트를 만들어 봤습니다.

실습을 통해서 useState, onChange, onClick, onKeyDown 같은 이벤트 처리 방식과 배열 상태 업데이트 방법을 조금 더 학습할 수 있어서 개인적으로는 되게 유익한 시간이었던 것 같아요!

 

핵심 개념

  • useState로 입력값과 리스트 항목 상태 관리
  • onChange로 입력값 업데이트
  • onClick + onKeyDown 항목 추가
  • map()으로 리스트 렌더링
  • 항목별 삭제
  • 항목별 좋아요 수 증가

ToDoList 컴포넌트 작성

import React, {useState} from 'react';

function ToDoList() {
    const [input, setInput] = useState('');     // 입력값 저장 상태
    const [todos, setTodos] = useState([]);     // 항목들을 저장하는 배열 상태

    // 항목 추가(enter)
    const addTodo = () => {
        if(input.trim() === '') return;     //빈 입력값 무시
        const newTodo = {
            id: Date.now(),     // 고유 ID 생성
            text: input,        // 사용자가 입력한 텍스트
            likes: 0,           // 초기 좋아요 수 = 0
        };
        setTodos([...todos, newTodo]);  // 기존 리스트에 항목 추가
        setInput('');   // 입력창 비우기
    };

    // 항목 삭제
    const deleteTodo = (id) => {
        // 선택 항목 제외 리스트 재구성
        setTodos(todos.filter((todo) => todo.id !== id));
    }

    // 좋아요 증가
    const likeTodo = (id) => {
        setTodos(
            todos.map((todo) =>
                // 해당 항목의 좋아요 수만 1증가, 나머지는 유지
                todo.id === id ? {... todo, likes: todo.likes + 1} : todo
            )
        );
    };

    // enter 키 입력 처리
    const handleKeyDown = (e) => {
        // enter키 입력 시 addTodo 실행
        if(e.key === 'Enter') addTodo();
    };

    reutrn (
        <div>
            <h2>To-Do 리스트</h2>

            {/* 입력창 : 입력값 + 키 입력 이벤트 연결 */}
            <input type="text" value={input}
                onChange={(e) => setInput(e.target.value)}
                onKeyDown={handleKeyDown} placeholder="할 일을 입력하세요." />

            {/* 추가 버튼으로 addTodo 실행 */}
            <button onClick={addTodo}>할 일 추가</button>

            {/* To-Do 항목 출력 */}
            <ul>
                {todos.map((todo) => (
                    <li key={todo.id}>
                        {todo.text}{' '}
                        <button onClick={() => deleteTodo(todo.id)}>삭제</button>
                        <button onClick={() => likeTodo(todo.id)}>좋아요</button>
                    </li>
                ))}
            </ul>
        </div>
    );
}

export default ToDoList;

 

App.js 작성

 

실행 화면(npm start)

좋아요 숫자 표시 + 삭제 버튼 클릭 시 확인 메시지 출력 기능 추가

 

 

좋아요 버튼은 항목당 한 번만 누를 수 있도록 제한하기

체크박스로 완료 처리 == 텍스트에 취소선 추가하기

 

검색창을 통해 오늘 할 일을 실시간 필터링 하기

 

 

 

전체 리스트 보기 / 완료된 리스트 보기 기능 추가하기

 

결과

 

 

기능 목록 정리

  • 할 일 추가 : useState, input, onChange, onKeyDown.
  • 항목 삭제(확인창 포함) : filter(), window.confirm().
  • 좋아요 버튼 + 1회 제한 : map(), 조건부 렌더링, disabled 속성.
  • 완료 체크 : map(), 상태 변경, textDecoration.
  • 검색창 : useState, includes(), toLowerCase().
  • 검색 + 필터링 동시 적용 : 복합 조건 필터링(filter, AND 조건).
  • 완료 항목만 보기 버튼 : 추가 상태 관리(filterType_, 조건 분기.

마무리..

useState() - 상태 관리는 React 컴포넌트에서 동적인 값을 저장하고, 관리할 수 있게 하는 훅(Hook)이다!

const [value, setValue] = useState(초기값);

예 : 입력값, 할 일 목록, 좋아요 상태, 검색어, 필터 상태 등 모두 useState로 관리.

 

이벤트 핸들링은 React에서 input, button 등 HTML 요소에 사용자 이벤트를 연결한다.

<input type="text" value={input}
	onChange={(e) => setInput(e.target.value)}
	onKeyDown={handleKeyDown} placeholder="할 일을 입력하세요." />
<button onClick={addTodo}>할 일 추가</button>

onChange >> 입력값 실시간 반영.

onClick >> 버튼 클릭 시 함수 실행.

onKeyDown >> Enter 키 입력 처리 등.

 

조건부 렌더링(삼항 연산자, &&, if 등)을 사용해 특정 조건에 따라 요소를 보여준다.

<span style={{textDecoration: todo.completed ? 'line-through' : 'none', 
	marginLeft: '10px', marginRight: '10px'}}>
	{todo.text}</span>
    
<button onClick={() => likeTodo(todo.id)} disabled={todo.liked}>좋아요</button>

삼항 연산자(조건 ? A : B)를 사용한 todo.completed가 true면 'line-through'(취소선) false면 'none'.

속성 기반 조건부 렌더링(disabled={조건})을 이용한 todo.liked가 true면 버튼 활성화(disabled) false면 클릭 가능.

 

리스트 렌더링(map) 배열을 반복해서 컴포넌트 형태로 렌더링.

{filteredTodos.map((todo) => (
	<li key={todo.id} style={{ margin: '8px 0' }}>
    ...
    </li>

filteredTodos는 filter()를 거친 할 일 목록 배열.

map()은 이 배열을 순회하면서 각 항목을 <li>로 렌더링.

각 항목마다 key 속성(여기서는 {todo.id})을 꼭 줘야 React가 변경 여부를 추적 가능하다.

**(Key가 필요한 이유 : React는 DOM을 효율적으로 업데이트 하기 위해 리스트 항목마다 고유 식별자인 key가 필요하다!)**

 

리스트 필터링(filter)은 배열에서 조건을 만족하는 항목만 반환한다.

검색 기능이나 완료 항목 보기 기능에서 사용.

// 실시간 필터링된 목록
const filteredTodos = todos.filter((todo) =>  {
    // 입력한 검색어를 소문자로 변환 > 현재 할 일에도 소문자로 변환해서 포함 여부 확인
    const matchesSearch = todo.text.toLowerCase().includes(searchTerm.toLowerCase());
    // 필터 타입이 all이면 모두 허용, completed면 완료된 항목만 허용
    const matchesFilter = filterType ==='all' || todo.completed;
    // 두 조건 모두 만족하는 항목만 필터링
    return matchesSearch && matchesFilter;
});

 

 

불변성 유지(... 전개 연산자)는 React에서 상태를 직접 수정하지 않고 새 배열/객체를 만들어서 상태를 업데이트 한다.

// 좋아요 증가
const likeTodo = (id) => {
    setTodos(
        todos.map((todo) =>
            todo.id === id && !todo.liked ?
            {... todo, likes: todo.likes + 1, liked: true} : todo
        )
    );
};

todo.id === id인 항목만 찾아서 ...todo로 기존 객체를 복사.

likes와 liked 값 덮어쓰기 후 나머지 항목은 변경 없이 유지.

 

**불변성을 지키지 않은 예 **

todos.find(todo => todo.id === id).likes++;		// 직접 변경 X
setTodos(todos);	// 같은 참조를 넘김 X

위의 예시인 경우 React가 변화 감지를 하지 못하고, 렌더링도 안된다.

 

입력 처리와 Enter 키

// enter 키 입력 처리
const handleKeyDown = (e) => {
    // enter키 입력 시 addTodo 실행
    if(e.key === 'Enter') addTodo();
};

 

검색 + 필터 복합 처리

const filteredTodos = todos.filter((todo) =>  {
    // 입력한 검색어를 소문자로 변환 > 현재 할 일에도 소문자로 변환해서 포함 여부 확인
    const matchesSearch = todo.text.toLowerCase().includes(searchTerm.toLowerCase());
    // 필터 타입이 all이면 모두 허용, completed면 완료된 항목만 허용
    const matchesFilter = filterType ==='all' || todo.completed;
    // 두 조건 모두 만족하는 항목만 필터링
    return matchesSearch && matchesFilter;
});

matchesSearch : 검색어가 todo.text 안에 포함되는지 (includes) 확인.

matchFilter : 전체 보기(all)인지 또는 완료된 항목만 보기(completed)인지 판별.

&& 조건 : 둘 다 true일 때만 항목을 유지해서 리스트에 보여준다.

 

** 상태(state) 기반의 UI 필터링 검색어와 보기 옵션 실시간 반영 **

*컴포넌트 정돈 + 유지보수의 용이성?*

 

 

추가 확장 갠념?

useEffect(데이터 저장/불러오기)

localStorage 연동

useMemo로 필터링 최적화

컴포넌트 분리(TodoItem, todoInput, ...)

 

지금까지의 To-Do 학습은 React의 기초부터 동적 기능을 처리하고, 사용자 입력, 상태 관리, 조건 렌더링, 리스트 조작까지 다양하게 연습할 수 있었던 프로젝트였습니다... React의 To-Do 리스트를 학습하면서 점점 다양한 기능을 추가해보고, 공부할 수 있었던 시간이여서 더 많은 기능을 공부해서 추가해보고 싶었지만 뇌 용량 초과로... 여기까지만 공부해보고 나머지 공부하지 못한 기능들은 언젠가 또 다른 공부를 할 때 배워볼게요!