이번 글에서는 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 리스트를 학습하면서 점점 다양한 기능을 추가해보고, 공부할 수 있었던 시간이여서 더 많은 기능을 공부해서 추가해보고 싶었지만 뇌 용량 초과로... 여기까지만 공부해보고 나머지 공부하지 못한 기능들은 언젠가 또 다른 공부를 할 때 배워볼게요!
'React' 카테고리의 다른 글
| [React] To-Do 리스트 : 컴포넌트 분리 (2) | 2025.07.31 |
|---|---|
| [React] 조건부 렌더링(로그인 + 회원가입) (0) | 2025.07.14 |
| [React] 배열을 이용한 반복 출력 (1) | 2025.07.14 |
| [React] 사용자 입력 처리(input + state + onChange) (2) | 2025.07.14 |
| [React] Props와 State, 버튼 클릭 이벤트 만들기 (2) | 2025.07.12 |