고양이와 코딩
[React] 투두리스트를 만들어보자 ! - 1 본문
앞으로 눈 감고도 투두리스트를 만들때까지. 투두리스트를 만들겠다!!!!!!
그 시작이 오늘 ㅎㅎ
리액트를 다루는 기술(개정) 기반으로 진행합니다 ~
src/components/
- TodoTemplate
- TodoInsert
- TodoListItem
- TodoList
import React from 'react';
const TodoTemplate = ({ children }) => {
return (
<div className="flex flex-col justify-center p-8">
<div className="text-xl">투두 -리스트 👾</div>
<div className="">{children}</div>
</div>
);
};
export default TodoTemplate;
TodoTemplate.js
import React from 'react';
import TodoListItem from './TodoListItem';
const TodoList = ({ todos }) => {
return (
<div className="flex flex-col justify-center border w-auto">
{todos.map((todo) => (
<TodoListItem todo={todo} key={todo.id} />
))}
</div>
);
};
export default TodoList;
TodoList.js
import React from 'react';
const TodoListItem = ({ todo }) => {
const { text } = todo;
return (
<div className="flex justify-between align-center border-y p-4">
<div className="text">{text}</div>
<div className="remove">X</div>
</div>
);
};
export default TodoListItem;
TodoListItem.js
import React from 'react';
const TodoInsert = () => {
return (
<form className="my-4 flex justify-between">
<input
type="text"
placeholder="할 일을 입력하세요"
className="border p-2 w-full"
/>
<button type="submit" className="mx-2 text-xl">
+
</button>
</form>
);
};
export default TodoInsert;
TodoInsert.js
import './App.css';
import TodoInsert from './components/TodoInsert';
import TodoTemplate from './components/TodoTemplate';
import TodoList from './components/TodoList';
import { useState } from 'react';
const App = () => {
const [todos, setTodos] = useState([
{
id: 1,
text: '밥 잘 챙겨먹기',
},
{
id: 2,
text: '다옴이 밥 주기',
},
{
id: 3,
text: '태보 하기',
},
]);
return (
<TodoTemplate>
<TodoInsert />
<TodoList todos={todos} />
</TodoTemplate>
);
};
export default App;
App.js
현재까지 코드에서 App.js에서 작성한 정적인 배열 데이터를
TodoList로 전달 -> TodoList에서 TodoListItem으로 전달 -> TodoListItem에서 렌더링
내가 이 부분을 엄청!! 헷갈려하는데,,, 하나하나 기록하며 기억하도록 하자 | ᐕ)੭*⁾⁾
그리고 TodoList에서 props로 받아왔던 todos 배열을 map을 통해 TodoListItem으로 이루어진 배열로 변환해주는 작업!
--> 이 부분도 한동안 왜????? 라며 엄청 의문을 가졌었는데, 천천히 생각을 안하고!! 무작정 코드만 따라쳐서 그랬던거다.. (정신차려)
1. 각 Todo 아이템들을 하나하나 관리 해 줘야 하기 때문에 고유 식별자인 `id`가 필요하다
2. `todos` 배열은 계속해서 늘어나고, (여러 todo 아이템을 포함하고 있음) 이를 화면에 표시하기 위해 반복해서 렌더링을 해 주어야 한다. `map` 함수는 각 배열의 요소를 순회하며 새로운 배열을 생성하기 때문에,, 사용한다!
항목 추가 기능 구현하기
현재 상태에서 일정을 추가하는 기능을 구현하려면,
- TodoInsert 컴포넌트에서 input 상태를 관리
- App 컴포넌트에는 todos 배열에 새로운 객체를 추가하는 함수를 만들어 주어야 함
import React, { useState, useCallback } from 'react';
const TodoInsert = () => {
const [value, setValue] = useState('');
const onChange = useCallback((e) => {
setValue(e.target.value);
console.log(e.target.value);
}, []);
return (
<form className="my-4 flex justify-between">
<input
type="text"
placeholder="할 일을 입력하세요"
className="border p-2 w-full"
value={value}
onChange={onChange}
/>
<button type="submit" className="mx-2 text-xl">
+
</button>
</form>
);
};
export default TodoInsert;
TodoInsert.js
이 컴포넌트에서 입력 상태를 관리해주기 위해 useState를 사용하여 value 라는 상태를 정의한다!
useCallback Hook을 사용하는 이유는, onChage 함수를 사용하는데, 컴포넌트가 리렌더링될 때마다 함수를 새로 만드는 것이 아닌, 한번 만들고 계속해서 재사용 하기 위함이다.
그리고 onChange 함수 내에 console.log(e.target.value)를 찍어보면
이처럼 개발자 도구 콘솔란에서 입력값이 제대로 들어오는것을 확인할 수 있다
todos 배열에 새 객체를 추가해보자
App 컴포넌트에서 todos 배열에 새 객체를 추가하는 onInsert 함수를 생성해야 한다.
이 함수에서는 새로운 객체를 생성 할 때마다, id 값에 1을 더해주어야 한다.
* 이 때 id를 useRef를 사용해 관리하는데, 그 이유는 id는 렌더링 되는 정보가 아니기 때문이다!! (단순히 참조되는 값)
// 고윳값으로 사용될 id
// ref를 사용하여 변수 담기
const nextId = useRef(4);
const onInsert = useCallback(
(text) => {
const todo = {
id: nextId.current,
text,
};
setTodos(todos.concat(todo));
nextId.current += 1; // 다음 아이디에 1씩 더하기
},
[todos],
);
App.js에 생성한 onInsert 함수. 혼자서 잘 안짜여지는 코드 투성이인(저는 그래요,,)
먼저
- `nextId` 변수는 useRef 훅을 사용하여 생성되었고, 지금은 임시 데이터가 3이 마지막이므로, 4를 초기 고유 id 값으로 정해 준 것
- `onInsert` 함수는 텍스트를 인수로 받아 새로운 Todo 항목을 생성하고, 이를 `todos` 배열에 추가한 다음,
`nextid.current` 값을 1 증가시켜서 다음에!! 추가될 Todo 항목의 id를 유지시킨다. - `setTodos` 함수를 호출하여 `todos` 상태를 업데이트 한다. concat 메서드를 사용하여 기존의 todos 배열와 새로운 todo 항목을 합침! (기존 내용에 새롭게 들어온거 더해서 반환한다는 말이며, 기존 배열을 변경하지 않음)
- `useCallback` 훅의 두 번째 매개변수는 의존성 배열로 우리는[todos]로 되어 있는데, 이는 todos 배열이 변경될 때마다 `onInsert` 함수가 새로 생성되도록 하는 역할을 함.
import './App.css';
import TodoInsert from './components/TodoInsert';
import TodoTemplate from './components/TodoTemplate';
import TodoList from './components/TodoList';
import { useState, useRef, useCallback } from 'react';
const App = () => {
const [todos, setTodos] = useState([
{
id: 1,
text: '밥 잘 챙겨먹기',
},
{
id: 2,
text: '다옴이 밥 주기',
},
{
id: 3,
text: '태보 하기',
},
]);
// 고윳값으로 사용될 id
// ref를 사용하여 변수 담기
const nextId = useRef(4);
const onInsert = useCallback(
(text) => {
const todo = {
id: nextId.current,
text,
};
setTodos(todos.concat(todo));
nextId.current += 1; // 다음 아이디에 1씩 더하기
},
[todos],
);
return (
<TodoTemplate>
<TodoInsert onInsert={onInsert} />
<TodoList todos={todos} />
</TodoTemplate>
);
};
export default App;
입력값 제출하기
App.js의 return 문 안에서 TodoInsert에 onInsert 함수를 전달해주었다. 이 함수에 useState로 관리하고 있는
value 값을 파라미터로 넣어서 호출 해 주자!
import React, { useState, useCallback } from 'react';
const TodoInsert = ({ onInsert }) => {
const [value, setValue] = useState('');
const onChange = useCallback((e) => {
setValue(e.target.value);
console.log(e.target.value);
}, []);
const onSubmit = useCallback(
(e) => {
onInsert(value);
setValue(''); // value 값 초기화
//submit 이벤트는 브라우저에서 새로고침을 발생시키므로,
// 이를 방지하기 위해 아래 함수 호출
e.preventDefault();
},
[onInsert, value],
);
return (
<form className="my-4 flex justify-between" onSubmit={onSubmit}>
<input
type="text"
placeholder="할 일을 입력하세요"
className="border p-2 w-full"
value={value}
onChange={onChange}
/>
<button type="submit" className="mx-2 text-xl">
+
</button>
</form>
);
};
export default TodoInsert;
대부분의 내용을 주석으로 설명 해 놓았다 (까먹지 말자 ~~)
물론 onSubmit이 아닌 onClick 이벤트로도 처리할 수 있지만, onSubmit은 버튼 클릭 이외에 Enter 버튼을 눌러도 작동하기 때문에,
사용자 입장에서 훨씬 편리하다 !! - ̗̀( ˶'ᵕ'˶) ̖́-
이렇게 일정 추가 기능까지 구현하였다 !!!!!
일정 삭제기능 구현하기
함수형 스터디를 하며 원본 배열을 변경하지 않는것!! (불변성)의 중요성에 대해 많이 배웠다.
filter() 메서드를 사용하면, 원본 배열을 변경하지 않고 필요한 배열 요소만 반환할 수 있다.
id 값은!! 바로 이때 사용된다.
const onRemove = useCallback(
(id) => {
setTodos(todos.filter((todo) => todo.id !== id));
},
[todos],
);
App.js에 onRemove 함수를 추가해 준다.
사용자가 선택한 일정의 id과 기존 일정의 id가 다른 것들만 쏙쏙 골라서 반환한다. (선택한 일정은 배열에서 제거된다)
return (
<TodoTemplate>
<TodoInsert onInsert={onInsert} />
<TodoList todos={todos} onRemove={onRemove} />
</TodoTemplate>
);
};
TodoList로 onRemove 함수를 전달 해 주었으므로,, TodoList로 이동해서 사용해 보자
import React from 'react';
import TodoListItem from './TodoListItem';
const TodoList = ({ todos, onRemove }) => {
return (
<div className="flex flex-col justify-center border w-auto">
{todos.map((todo) => (
<TodoListItem todo={todo} key={todo.id} onRemove={onRemove} />
))}
</div>
);
};
export default TodoList;
또 TodoListItem으로 id와 onRemove 함수를 전달 해 주었으므로, 가본다
import React from 'react';
const TodoListItem = ({ todo, onRemove }) => {
const { id, text } = todo;
return (
<div className="flex jd, ustify-between align-center border-y p-4">
<div className="text">{text}</div>
<div onClick={() => onRemove(id)}>X</div>
</div>
);
};
export default TodoListItem;
마지막으로 X 버튼에 onClick 이벤트를 달아서, 버튼을 누를 시 onRemove함수가 호출되며 todo.id가 전달되도록 하면 된다.
이렇게 하면 간단하게 일정 추가, 삭제를 할 수 있는 투두리스트가 완성된다!!!
아직 눈 감고 일정 추가 삭제를 만들지 못하므로,,,
수정 기능과, 로컬스토리지에 일정을 저장해서 관리하는 기능은 차차 만들어 보려고 한다 !!
'todoList' 카테고리의 다른 글
[React] 친구가 말아준 TodoList 과제 (0) | 2024.03.03 |
---|