한 걸음씩
[JS] To Do List 본문
TMI. 이전에 투두리스트를 만들고 깃허브에 업로드를 했었는데 로컬 스토리지에 저장은 되지만 불러오기를 구현을 못했었는데
코드를 짤 때 할 일을 생성하는 코드와 할 일을 화면에 보여주는 코드를 하나의 함수에 작성해서 로컬 스토리지에서 불러오려면 거의 코드를 새로 짜야하는 상황이었다. 간단히 말해 함수 분리를 잘못해서 구현을 못한 것... 그래서 다시 새로 구현했다.

// index.html
<body>
<div id="title">To Do List</div>
<!-- form을 사용하면 엔터만으로 서버에 데이터를 보낼수 있기 때문에 편리함 -->
<form id="todo-form">
<input type="text" placeholder="할 일을 입력하세요">
</form>
<ul id="todo-list">
<!-- <li><span></span></li> -->
</ul>
<script src="index.js"></script>
</body>
이전 투두리스트 만들었을 때는 input태그와 button 태그 전부 따로 만들었고 이벤트 리스너도 각각 만들어줬어야했는데
이번에는 form태그를 사용했다.
form 태그는 input에서 text를 입력하고 enter만으로 서버에 데이터를 보내기 때문에 편리하다.
// index.js
const toDoForm = document.querySelector('#todo-form')
const toDoInput = document.querySelector('#todo-form input')
const toDoList = document.querySelector('#todo-list')
// 4. 로컬 스토리지에 저장
let toDos = []
const TODOS_KEY = 'todos'
function saveToDos(){
// JSON.stringify는 문자열로 바꾸는 것
// 왜? 로컬스토리지에서는 문자열밖에 저장을 못함
localStorage.setItem(TODOS_KEY, JSON.stringify(toDos))
}
// 3. 삭제
function deleteTodo(event){
// target은 HTML element인데 HTML element에는 하나 이상의 property가 있다
// parentElement/parentNode는 클릭된 event(li요소)의 부모 요소
// innerText 해서 출력해보면 클릭된 li의 텍스트를 볼 수 있다.
// console.dir(event.target.parentNode.innerText)
const li = event.target.parentNode
li.remove() // 화면상에서만 할 일 삭제
toDos = toDos.filter(toDo => toDo.id !== parseInt(li.id)) // 6. 로컬에서 삭제
// 사용자가 클릭한 li.id와 다른 toDo는 남김 ->
// 기존의 toDos 배열에 삭제한 todo를 제외한 새 배열을 할당
saveToDos() // 다시 로컬스토리지에 업데이트 해야함. -> 저장
}
// 2. newTodo 값을 받아서 화면에 할 일 생성
function paintToDo(newTodo){
const li = document.createElement('li') // li태그 생성
// newTodoObj 객체를 전달받아 newTodo 인수로 받아와서 사용
li.id = newTodo.id // newTodoObj의 id값을 가리킴
const span = document.createElement('span') // span태그 생성
span.innerText = newTodo.text
const button = document.createElement('button') // 삭제버튼 생성
button.innerText = 'X'
button.addEventListener('click', deleteTodo)
// <ul><li><span></span></li></ul> 구조만들기
li.appendChild(span)
li.appendChild(button)
toDoList.appendChild(li)
}
// 1. input 관리
function handleToDoSubmit(event){
event.preventDefault() // 입력칸에 텍스트 입력 후 제출안되고 남아있음
const newTodo = toDoInput.value // 입력칸 비우기 전 값을 newTodo변수에 저장하기
toDoInput.value = '' // 제출 후 입력칸 비우기
const newTodoObj = {
text: newTodo,
id: Date.now(), // 각 Todo에 대해 고유 번호 부여
}
toDos.push(newTodoObj) // 할 일을 배열에 push
paintToDo(newTodoObj) // paintToDo 함수에 객체 형태로 보내기 ; 할 일을 화면에 그리기
saveToDos() // 로컬 스토리지에서 아이템 가져오기
}
toDoForm.addEventListener('submit', handleToDoSubmit)
// 5. 로컬스토리지에서 아이템 가져오기
const savedToDos = localStorage.getItem(TODOS_KEY)
if(savedToDos){
const parsedToDos = JSON.parse(savedToDos) // 배열로 가져오기 ; 문자열 -> 배열
toDos = parsedToDos // 이전의 todo를 복원
// 배열의 아이템을 하나씩 순회할 필요X
// 왜? paintToDo라는 함수에서 할 일을 하나씩 생성할거니까
parsedToDos.forEach(paintToDo) // 각각의 할 일에 대해서 실행
}
함수 작성 순서를 다 매겨놓았기 때문에 천천히 읽어보면 이해할 수 있을 거다.
이전 코드와 비교해서 간결하고 가독성도 높아졌다.
input관리 함수, 화면에 할 일 생성 함수, 삭제 함수, 저장 함수, 불러오기 함수
이렇게 다 따로 관리하니까 에러가 발생했을 때 어느 지점에서 문제가 생겼는지 찾기도 수월했다.
로컬 스토리지에 저장하는 부분까지는 이전에도 구현해 봤기 때문에 문제가 없었는데 getItem으로 불러올 때와 해당할 일을 로컬에서까지 삭제하는 부분이 좀 어려웠다.
5. 로컬스토리지에서 아이템 가져오기
로컬에 저장할 때 배열 → 문자열로 바꿔서 저장했기 때문에 불러올 때도 문자열 → 배열로 바꿔서 들고 와야 한다.
그래서 parse()라는 메서드를 사용한다. 그런데 toDos 배열에 parse 한 배열을 재할당해줘야 하는데 이 말은 즉, 이전에 로컬에 저장되어 있던 할 일 데이터를 다시 가져와 할당을 해줘야 한다. 그렇지 않으면 로컬에는 저장되어 있지만 화면으로 불러오지 못하게 된다.
그리고 forEach로 하나씩 순회하는 메서드는 paintToDo로 넘겨서 동작하게 하면 된다. 왜냐하면 paintToDo 함수에는 할 일을 생성하는 코드가 있기 때문이다.
3. 삭제
화면상에서 할 일을 삭제하는 것은 해당 event 객체의 부모를 찾아서 remove()하면 된다.
왜 부모요소를 찾아서 삭제를 해야 할까? 할 일을 생성한 태그는 span인데 span의 부모는 li이다. li 태그를 삭제해야 해당할 일을 깔끔하게 삭제한 것이기 때문이다.
하지만 로컬스토리지에서 삭제를 하려면 조금 복잡해지는데 로컬스토리지에서 할 일이 저장된 배열을 보면 같은 할 일이 생성되어 있을 때 구별을 할 수가 없다는 것이다. 이것을 해결하려면 각 할 일마다 고유번호를 생성해줘야 한다.
그래서 handleToDoSubmit함수에서 객체를 생성해 주는데 text 키는 할 일을 저장하고 id는 Date.now()로 고유 값을 부여해 준다.
const newTodoObj = {
text: newTodo,
id: Date.now(), // 각 Todo에 대해 고유 번호 부여
}
toDos.push(newTodoObj) // 할 일을 배열에 push
paintToDo(newTodoObj) // paintToDo 함수에 객체 형태로 보내기 ; 할 일을 화면에 그리기
saveToDos() // 로컬 스토리지에서 아이템 가져오기
그리고 toDos 배열, paintToDo함수에 넘겨주는 값을 객체 변수로 바꿔야 한다.
또한 paintToDo함수에서도
li.id = newTodo.id
span.innerText = newTodo.text
위와 같이 코드를 재작성해야 한다.
마지막으로 deleteTodo함수에서 로컬 스토리지에서 삭제하는 코드를 작성해 주면 되는데 이때 filter 메서드를 사용하면 간단하다.
toDos = toDos.filter(toDo => toDo.id !== parseInt(li.id)) // 6. 로컬에서 삭제
// 사용자가 클릭한 li.id와 다른 toDo는 남김 ->
// 기존의 toDos 배열에 삭제한 todo를 제외한 새 배열을 할당
saveToDos() // 다시 로컬스토리지에 업데이트 해야함. -> 저장
toDos 배열을 filter 해서 해당 요소의 id가 삭제하려는 부모 요소의 id와 같다면 삭제하고, 같지 않은 부분만 toDos 배열에 재할당하여 saveToDos 함수, 로컬 스토리지에 저장을 업데이트해줘야 한다.
삭제하려는 부모 요소의 id값에 parseInt 정수형으로 변환해 주는 이유는 문자열 형태이기 때문이다.
https://github.com/winterkang/JavaScript-Challenge/tree/main/TODO-LIST
GitHub - winterkang/JavaScript-Challenge
Contribute to winterkang/JavaScript-Challenge development by creating an account on GitHub.
github.com
'JS' 카테고리의 다른 글
| [JS] Number Counter 만들기 (0) | 2023.07.06 |
|---|---|
| [JS] Random Color 2 - color flipper 만들기 (0) | 2023.07.06 |
| [JS] weather API and Date (0) | 2023.07.02 |
| [JS] canvas 2 - meme maker, text, save (0) | 2023.07.01 |
| [JS] Canvas 1 - making canvas (0) | 2023.06.30 |