미니프로젝트 요구사항이
1. 투두리스트의 기본 조건
2. 시계
3. 타이머
다음과 같았는데, 3가지를 한번에 공부하기 보다,
하나씩 분리해서 공부해보려고 한다.
우선 1번부터, 챗지피티와 차근 차근 한 줄씩 따라 연습해봤음
공부 방향은
전체 적인 흐름을 보고 훑었고, 챗지피티랑 따라 치면서 기억하고 생각해보고, 그 뒤 다시 한번 스스로 쳐보기 도전 방향임
생각보다 시간이 많이 걸릴 것이기때문에
하지만 뭐든 익숙해지기까지 반복이 중요하단 것. 🥺
너무 처음부터 완벽히 해내려면, 시작이 어려우니까.. 일단 어떻게든 코드를 쳐보며 익혀보자.
화면에 출력될 투두리스트 내용은 다음과 같다.
[할 일 입력하세요] [추가]
---------------------------
• 공부하기 [삭제]
• 운동하기 [삭제]
• 리액트 연습하기 [삭제]
구현 할 내용은,
할 일을 입력할 인풋창과
그 내용을 추가 버튼을 누를 경우, 아래 리스트에 나열되고
삭제 버튼이 함께 사용 되어, 삭제를 누를 경우, 리스트에서 삭제 되는 방식
쉬운 기능 같아도, 은근히 생각할 거리가 많다.
하지만 기초 중에 기초 내용이니까.. 해야지 뭐.
공부하게 되면서, 헷갈렸던 내용들도 짚고 가며 미루지말고 오늘하자 !
최종 코드는 아래와 같다
import React, { useState } from 'react';
function App() {
const [inputValue, setInputValue] = useState('');
const [todos, setTodos] = useState([]);
const handleInputChange = (e) => {
setInputValue(e.target.value);
};
const handleAddTodo = () => {
if (inputValue.trim() === '') return;
setTodos([...todos, inputValue]);
setInputValue('');
};
const handleDeleteTodo = (index) => {
const newTodos = todos.filter((_, i) => i !== index);
setTodos(newTodos);
};
return (
<div style={{ padding: '20px' }}>
<input
type="text"
value={inputValue}
onChange={handleInputChange}
placeholder="할 일 입력하세요"
/>
<button onClick={handleAddTodo}>추가</button>
<ul>
{todos.map((todo, index) => (
<li key={index} style={{ margin: '8px 0' }}>
{todo}
<button
onClick={() => handleDeleteTodo(index)}
style={{ marginLeft: '10px' }}
>
삭제
</button>
</li>
))}
</ul>
</div>
);
}
export default App;
각 파트 별로 어떤 역할을 하는지, 주석을 요청 해서 쉽게 흐름을 읽어보자
// React와 useState Hook을 가져옴
import React, { useState } from 'react';
// App이라는 함수형 컴포넌트 정의
function App() {
// inputValue : 입력창의 값 저장
// setInputValue : inputValue를 변경하는 함수
const [inputValue, setInputValue] = useState('');
// todos : 할 일 목록 배열
// setTodos : todos를 변경하는 함수
const [todos, setTodos] = useState([]);
// 입력창의 값이 바뀔 때마다 호출되는 함수
const handleInputChange = (e) => {
// 입력값을 inputValue 상태에 저장
setInputValue(e.target.value);
};
// '추가' 버튼 클릭 시 호출되는 함수
const handleAddTodo = () => {
// 입력값이 공백만 있는 경우 추가 안함
if (inputValue.trim() === '') return;
// 기존 todos 배열에 새로운 입력값 추가
setTodos([...todos, inputValue]);
// 입력창 초기화
setInputValue('');
};
// 특정 할 일을 삭제하는 함수
const handleDeleteTodo = (index) => {
// 클릭한 할 일의 index를 제외한 새 배열 만들기
const newTodos = todos.filter((_, i) => i !== index);
// 새 배열로 할 일 목록 갱신
setTodos(newTodos);
};
// 화면에 보여질 내용 반환 (JSX)
return (
<div style={{ padding: '20px' }}>
{/* 할 일 입력창 */}
<input
type="text"
value={inputValue} // 입력값은 inputValue로 설정
onChange={handleInputChange} // 값이 변할 때 handleInputChange 호출
placeholder="할 일 입력하세요" // 힌트 텍스트
/>
{/* 추가 버튼 */}
<button onClick={handleAddTodo}>추가</button>
{/* 할 일 목록 */}
<ul>
{/* todos 배열을 하나씩 꺼내서 화면에 보여줌 */}
{todos.map((todo, index) => {
// return을 명시적으로 추가해서 JSX 반환
return (
<li key={index} style={{ margin: '8px 0' }}>
{todo}
{/* 삭제 버튼 */}
<button
onClick={() => handleDeleteTodo(index)} // 클릭 시 해당 할 일 삭제
style={{ marginLeft: '10px' }}
>
삭제
</button>
</li>
);
})}
</ul>
</div>
);
}
// App 컴포넌트를 다른 곳에서 사용할 수 있도록 내보내기
export default App;
인과관계가 궁금하다면,, 아래 코드를 참고 해보세요
// 1. React와 useState Hook을 가져옴
import React, { useState } from 'react';
// 2. App 함수형 컴포넌트 정의
function App() {
// 3. inputValue 상태 변수와 그 값을 설정하는 함수 setInputValue 정의
// 사용자가 입력한 값을 저장
const [inputValue, setInputValue] = useState('');
// 4. todos 상태 변수와 그 값을 설정하는 함수 setTodos 정의
// 할 일 목록을 저장
const [todos, setTodos] = useState([]);
// 5. 입력창의 값이 바뀔 때마다 호출되는 함수 handleInputChange
const handleInputChange = (e) => {
// 5.1. 사용자가 입력한 값을 inputValue 상태에 저장
setInputValue(e.target.value);
};
// 6. '추가' 버튼 클릭 시 호출되는 함수 handleAddTodo
const handleAddTodo = () => {
// 6.1. 입력값이 공백만 있는 경우는 할 일 추가를 하지 않음
if (inputValue.trim() === '') return;
// 6.2. 기존 todos 배열에 사용자가 입력한 새로운 할 일을 추가
setTodos([...todos, inputValue]);
// 6.3. 입력창 초기화 (새로운 할 일을 추가 후 입력창 비우기)
setInputValue('');
};
// 7. 특정 할 일을 삭제하는 함수 handleDeleteTodo
const handleDeleteTodo = (index) => {
// 7.1. 클릭한 할 일의 index를 제외한 새로운 배열 생성
const newTodos = todos.filter((_, i) => i !== index);
// 7.2. 새 배열로 할 일 목록 갱신
setTodos(newTodos);
};
// 8. 화면에 보여질 내용 반환 (JSX)
return (
<div style={{ padding: '20px' }}>
{/* 9. 할 일 입력창 */}
<input
type="text"
value={inputValue} // 9.1. 입력값은 inputValue 상태로 설정
onChange={handleInputChange} // 9.2. 값이 변할 때 handleInputChange 함수 호출
placeholder="할 일 입력하세요" // 9.3. 입력창에 힌트 텍스트 제공
/>
{/* 10. 추가 버튼 */}
<button onClick={handleAddTodo}>추가</button>
{/* 11. 할 일 목록 출력 */}
<ul>
{/* 12. todos 배열을 하나씩 꺼내서 화면에 표시 */}
{todos.map((todo, index) => {
// 12.1. 각 할 일 항목에 대해 return으로 JSX 반환
return (
<li key={index} style={{ margin: '8px 0' }}>
{todo}
{/* 13. 삭제 버튼 */}
<button
onClick={() => handleDeleteTodo(index)} // 13.1. 클릭 시 해당 할 일 삭제
style={{ marginLeft: '10px' }}
>
삭제
</button>
</li>
);
})}
</ul>
</div>
);
}
// 14. App 컴포넌트를 다른 곳에서 사용할 수 있도록 내보내기
export default App;
자 이제 한줄씩 연습해보며 공부해보기
나는 리턴 파트 먼저 쓰는게 편해서, 리턴으로 어떤게 출력될지, 먼저 써보기도 했다.
const [inputValue, setInputValue] = useState('');
설명
여기서 inputValue라는 **상태(state)**를 만들어요.
- inputValue : 지금 인풋창에 적힌 값
- setInputValue : 이 값을 바꿔주는 함수
- useState('') : 처음엔 빈 문자열로 시작
ex)
지금 인풋에 공부하기를 입력하면
→ inputValue 값이 공부하기로 저장돼요.
const [todos, setTodos] = useState([]);
설명
이번엔 todos라는 투두 목록 배열 상태를 만들어요.
- todos : 할 일들을 담는 배열
- setTodos : 그 배열을 바꿔주는 함수
- useState([]) : 처음엔 비어 있는 배열로 시작
ex)
["공부하기", "운동하기"] 이런 식으로 들어갈 거예요.
const handleInputChange = (e) => {
setInputValue(e.target.value);
};
설명
- handleInputChange : 인풋 내용이 바뀔 때 실행되는 함수
- (e) : 이벤트 객체 (인풋 태그에서 무슨 일이 일어났는지 알려주는 애)
- e.target.value : 인풋에 적힌 현재 값
- setInputValue로 그 값을 inputValue에 저장
ex)
인풋에 리액트 연습이라고 적으면
→ inputValue가 리액트 연습으로 바뀜!
const handleAddTodo = () => {
if (inputValue.trim() === '') return;
setTodos([...todos, inputValue]);
setInputValue('');
};
설명
- handleAddTodo : "추가" 버튼을 누를 때 실행되는 함수
- if (inputValue.trim() === '') return;
→ 인풋이 비어 있으면 아무것도 안 하고 그냥 종료 - setTodos([...todos, inputValue]);
→ 기존 todos 배열에 inputValue를 추가해서 새로운 배열로 만듦 - setInputValue('');
→ 인풋창 비워주기
ex)
공부하기 입력 후 추가 버튼 누르면
→ todos 배열이 ["공부하기"]가 되고
→ 인풋창은 다시 빈 칸!
추가 학습 :
- inputValue → 지금 입력창에 적혀있는 값이야.
- .trim() → 입력창에 만약 공백만 입력되어 있어도, 그걸 없애고 순수한 글자만 남겨. 트림의 기능이, 글자도 없는 거면.. 공백으로 판단 하는거네
- 예: ' 공부하기 ' → '공부하기'
- 예: ' ' → '' (아무 글자도 없게 됨)
- === '' → 이걸로 글자가 아무것도 없으면 확인하는 거야.
- return; → 만약 진짜 비어있으면 여기서 함수 멈추고 나가.
→ 즉, 아무것도 실행 안 하고 바로 종료!
글자도 없는 거면 결국 ''(빈 문자열)로 바뀌니까 → 공백으로 판단 하는 거야!
이걸 === ''로 검사하면
“아무 글자도 없는 상태”인지 확인하는 것이 되는 거지.
📌 한 가지 더! 만약 trim() 안 쓰면 ' ' 같은 공백만 있는 것도 값으로 인식해서 할 일로 추가해버릴 수 있어.
그래서 공백만 쓴 것도 무시하려고 trim()을 써주는 것
const handleDeleteTodo = (index) => {
const newTodos = todos.filter((_, i) => i !== index);
setTodos(newTodos);
};
설명
- handleDeleteTodo : 삭제 버튼 누를 때 실행되는 함수
- (index) : 몇 번째 항목인지 받아옴
- todos.filter((_, i) => i !== index)
→ todos 배열에서 index와 같은 위치는 빼고 새로운 배열 만듦 - setTodos(newTodos)
→ 새로운 배열로 다시 업데이트
ex)
["공부하기", "운동하기", "코딩하기"] → 1번(운동하기) 삭제
→ ["공부하기", "코딩하기"]
이건 filter 안에 들어가는 조건 함수야.
하나씩 뜯어보면
📌 filter()가 뭐냐면
- 배열을 돌면서
- true인 애들만 새 배열로 남기는 것
💗filter 함수는 원래 이렇게 생겼어
자바스크립트에서 filter는 콜백 함수를 실행하는데,
그 콜백 함수에 이렇게 세 개의 인자를 전달해.
array.filter((value, index, array) => {
// 조건을 반환 (true/false)
});
📌 ( , i) 이건 뭔가요?
- filter 안의 함수는 두 개의 값을 받아:
- 첫 번째 값 → 배열 안의 값 (여기선 todo)
- 두 번째 값 → 그 값의 인덱스 번호 (몇 번째인지)
그런데 우리는 값은 안 쓰고 인덱스만 쓸 거라서
첫 번째 자리는 _로 비워놓고, 두 번째만 i로 쓴 거야.
💗 왜 _를 썼냐면
매개변수는 자리를 무조건 채워줘야 해서
안 쓸 거면 의미 없는 변수라도 적어야 해.
보통 이런 상황에 **慣例(관례)로 _**를 써.
(_, i) => i !== index
여기서 _는 "나는 이 값 안 쓸 거예요" 라는 약속 같은 거야!
여기서 말하는 i는 **todos.filter가 배열을 하나씩 순회할 때마다 자동으로 전달해주는 "현재 요소의 인덱스 번호"**야.
<input
type="text"
value={inputValue}
onChange={handleInputChange}
placeholder="할 일 입력하세요"
/>
<button onClick={handleAddTodo}>추가</button>
- <input />
- type="text" : 텍스트 입력창
- value={inputValue} : 상태로 연결된 값
- onChange={handleInputChange} : 글자 입력될 때마다 값 변경
- placeholder : 아무것도 없을 때 흐리게 보여줄 글씨
- <button>
- onClick={handleAddTodo} : 클릭했을 때 할 일 추가
ex)
인풋에 공부하기 적고 → "추가" 누르면
→ 리스트에 추가됨!
할 일 목록(todos) 리스트를 화면에 뿌려주는 부분이에요.
<ul>
{todos.map((todo, index) => (
return(
<li key={index}>
{todo}
<button onClick={() => handleDeleteTodo(index)}>삭제</button>
</li>
)
))}
</ul>
설명
- <ul> : 리스트 전체를 감싸는 태그
- {todos.map((todo, index) => ( ... ))}
→ todos 배열을 순회하며 li로 하나씩 표시 - <li key={index}>
→ 각각의 리스트 항목, key는 React가 구분하기 쉽게 - {todo}
→ 할 일 텍스트 보여주기 - <button onClick={() => handleDeleteTodo(index)}>삭제</button>
→ 삭제 버튼, 누르면 해당 인덱스 항목 삭제
() => handleDeleteTodo(index)
이렇게 익명 함수(arrow function) 를 만들었죠.
→ 이 방식은 버튼을 클릭했을 때만 handleDeleteTodo를 호출하고, 그때 index를 전달해주는 거예요.
✓ 그래서 이렇게 하면 각 todo의 index 값이 handleDeleteTodo 함수로 전달돼서,
function handleDeleteTodo(index) {
// 해당 index의 todo를 삭제하는 코드
}
이런 식으로 삭제할 todo를 정확히 구분해서 지울 수 있어요.
지금 상황에서는 삭제할 todo를 구분해야 하니까
→ 이걸 써야 해요!
ex)
공부하기 [삭제]
운동하기 [삭제]
이렇게 쭉 나옴!
* 리액트에서 map() 함수는 배열을 반복하면서 각 요소에 대해 변환을 적용할 때 사용됩니다. map() 함수는 기본적으로 새로운 배열을 반환하기 때문에, 그 함수 안에서 반드시 return이 있어야 합니다.
예를 들어, JSX를 렌더링하는 경우 return이 필수적입니다:
첫번째 투두의 역할
→ todo의 역할
이 코드는 todos라는 배열을 .map() 함수를 사용해 하나씩 순회하면서
각 요소를 todo라는 이름으로 가져오는 거예요.
예를 들어 todos 배열이 아래와 같다고 해볼게요:
const todos = ["공부하기", "운동하기", "코딩하기"];
이렇다면, 이렇게 맵으로 하나하나 순회하는
todos.map((todo, index) => ( ... ))
것을 보여주는게 todo!
반복할 때마다
- 첫 번째 반복: todo는 "공부하기"
- 두 번째 반복: todo는 "운동하기"
- 세 번째 반복: todo는 "코딩하기"
이렇게 현재 순회 중인 값을 todo로 받아오는 거죠.
🤍→투두스 배열을 맵으로 돌면서, 배열에 있는 하나 하나 항목들을 li로 보여주는데, !
보여주는게 todo 하나하나씩.
근데 {} 중괄호를 쓴 이유는,
JSX에서는
→ 자바스크립트 변수, 값, 함수 실행 결과를 화면에 출력할 때
→ 반드시 {} 안에 넣는다!
그게 todo가 중괄호 안에 있는 이유예요.
그냥 <li> todo </li> 이렇게 적으면 todo 문자열..
그래도 투두리스트의 리스트 나열하기는 어느정도 이해한 것 같다.
역시 뭐든 기초가 제일 중요하다..
'Studying > 미니 프로젝트' 카테고리의 다른 글
mini project - 영화 페이지 만들기(3단계) (1) | 2025.05.15 |
---|---|
mini project - 영화 페이지 만들기 (2단계) (0) | 2025.05.05 |
mini project - 영화 페이지 만들기 (1단계) (0) | 2025.05.02 |
미니프로젝트의 두번째 기능 - 시계 구현 해보기 (0) | 2025.04.15 |
Mini project - 계산기 만들기 / 트러블 슈팅. 스스로 피드백 해보기 / 보완중 (2) | 2025.03.30 |