본문 바로가기

Language & Framework/JavaScript

TodoList 새로고침해도 내용이 유지되도록 기능 개발하기 (feat.localStorage)

리액트를 공부하다가 벽을 만났는데 멘붕 와서 다른 것 뒤적거리다가 드림코딩 슬랙 채널에서 기존 드림 코딩 강의에서 만든 TodoList에 localStorage를 적용할 방법을 찾지 못해서 해메고 계시는 드림 코더분의 글을 보게 되었다.

리액트 배운 지 며칠 되었다고 벌써 머리속에서 바닐라 자바스크립트가 잊혀져가던 참이고 리액트도 꼴보기 싫어서 잠시 머리 식힐 겸 도와드리기 위해 이 글을 작성하게 됐다.

 

기존 코드는 입력값을 전달 받아 미리 준비된 HTML에 전달 받은 값을 그냥 박아서 화면에 띄워주기만 하면 되는 상태였다면 localStorage를 사용하기 위해서는 전달 받은 값과 그리고 그 값의 id를 미리 오브젝트로 만들어 보관해야 한다는 사실만 생각하면 딱히 어려울 것이 없다.

 

강의에서 알려주는대로 코드를 작성하지 않고 내 마음대로 코드를 작성해서 다른 부분들이 있을 텐데 근본적인 건 변함 없으니 대충 이해하고 넘어가면 될 것 같다.

 

진행 순서에 따라 기존 코드를 어떻게 수정해야 할 지 알아보도록 하자.

 

 

1. 투두 리스트의 입력 값을 담당하는 핸들러

 

 

 

 

 

 

 

 

수정할 부분

-> textInput.value와 id를 묶어서 오브젝트로 만들어 addToDo에 전달해줄 것이다.

-> 기존에는 전역 변수에 let counter = 0 을 지정해준 뒤 addTodo 함수에서 요소를 생성하면 counter를 1씩 더해주는 방식으로 아이디를 구별했는데, 이러면 새로고침 시 counter가 다시 0부터 시작하기 때문에 기존에 localStorage에 저장한 오브젝트와 아이디가 겹치게 된다. 다른 방식으로 아이디를 생성할 것이다. 

-> 그리고 생성한 객체를 바로 localStorage에 저장하는 것까지 submitHandler에서 끝내주도록 하자.

 

+ ChangeTaskNum은 아마 강의에는 없었던 걸로 기억하는데 그냥 투두 총 개수를 카운트해주는 함수니까 무시하면 된다.

 

let todoArr = []; // const 안됩니다. 무조건 let~~

function handleSubmit(e) {
  e.preventDefault();
  console.log(textInput.value);
  if (textInput.value == "") {
    return false;
  }
  const newTodoItem = {
    id: Date.now(),
    text: textInput.value,
  };
  todoArr.push(newTodoItem);
  localStorage.setItem("todoItems", JSON.stringify(todoArr));

  addToDo(newTodoItem);

  ChangeTaskNum();
  textInput.value = " ";
}

수정 결과

-> newTodoItem이라는 오브젝트를 만들어서 id는 현재 시간을 ms로 나타낸 값을 임시로 사용했다. 이건 절대 겹치지 않는 난수 같은 게 아니라 id로 부적합할 수도 있지만 당장 투두리스트 사용하는 수준에서는 아무 문제 없을 것이다.

또한 text는 textInput.value를 그대로 가져왔다. 그럼 이제 우리가 필요한 텍스트 내용과 이를 구별할 수 있는 id가 갖춰졌으니 모든 준비는 끝났다.

-> 이 오브젝트를 todoArr에 push해준다. 

-> 그리고 todoArr을 localStorage에 저장한다. (새로 만든 오브젝트 자체를 저장하면 기존 값이 지워짐)

-> 마지막으로 오브젝트를 addToDo의 인자로 전달해준다.

 

+ JSON.stirngify는 JSON은 오직 string만 읽을 수 있으며 배열만 저장하기 때문에 객체를 key와 value로 string화해서 저장해주는 것이다.

 

 

2. addToDo

수정할 부분

-> idCount는 이제 없으니 삭제하자.

-> newTodoItem을 인자로 받아왔으니 text와 id값도 변경해 주어야 한다.

-> 삭제 버튼에 deleteHandler를 만들어주자.

 

function addToDo(newTodoItem) {
  const todoItem = document.createElement("div");
  todoItem.setAttribute("class", "todoList__main__item");
  todoItem.setAttribute("id", `${newTodoItem.id}`);

  const item = document.createElement("p");
  item.setAttribute("class", "todoList__main__item--text");
  item.innerText = `${newTodoItem.text}`;

  const button = document.createElement("button");
  button.setAttribute("class", "trash deleteBtn fa-solid fa-trash-can");
  button.addEventListener("click", (e) => {
    deleteHandler(todoItem, e);
  });

  todoItem.append(item);
  todoItem.append(button);
  todoList.append(todoItem);
}

-> 여기는 별 거 없고 드림코더분의 코드 환경과 맞추기 위해 엘리 강사님 스타일로 코드를 모두 변경했다.

 

 

3. deleteHandler

강의에서는 deleteHandler가 없었고 그냥 addToDo 함수 내에서 todoitem.remove() 붙여주고 끝이였던 것으로 기억한다.

코드가 길어지니까 함수를 만들어서 밖으로 꺼내주자.

 

우리가 deleteHandler에서 해야할 일은 2가지다. 

우선 해당 요소를 삭제해야 하고 해당 요소와 같은 id를 가진 오브젝트를 localStorage에서 삭제해야 한다.

그런데 우리가 localStorage에서 바로 원하는 값만 찾아서 삭제하는 것은 어려우니 약간 돌아서 가보도록 하자.

 

우리의 todoArr은 localStorage와 완전히 같은 값을 가진 배열이다.

따라서 todoArr에서 삭제한 요소와 같은 아이디 값을 가진 오브젝트를 삭제한 뒤 localStorage에 todoArr을 덮어씌워버리면 된다.

 

function deleteHandler(todoItem, e) {
  let targetId = e.target.parentElement.id;
  todoList.removeChild(todoItem);

  todoArr = todoArr.filter((todo) => todo.id !== parseInt(targetId));
  localStorage.setItem("todoItems", JSON.stringify(todoArr));
}

 

우선 인자로 받아온 todoItem을 이용해서 todoList에서 요소를 삭제한다. 굳이 인자로 받아와서 삭제하는 게 번거로우면 e.target..어쩌고 해도 상관 없다.

그리고 filter 함수를 사용하는데 filter 함수는 말 그대로 조건에 맞는 값을 가진 값들만 필터링해서 새로운 배열을 반환해주는 함수이다.

ex )  [ [ { id : 1, val : 1 } ], [ { id : 2, val : 2 } ], [ { id : 3, val : 3 } ], [ { id : 4, val : 14} ] ].filter((arr) => arr.id < 3);

// 반환값 -> [ [ { id : 1, val : 1 } ], [ { id : 2, val : 2 } ] ]

따라서 todoArr = todoArr.filter()를 이용하면 삭제한 요소와 같은 아이디를 가진 오브젝트가 삭제된 배열을 todoArr에 덮어 씌우게 되는 것이다. parseInt를 사용하는 이유는 targetId의 type이 String이기 때문에 그냥 비교하면 !==가 무조건 true가 되기 때문이다. 꼭 변경해줘야 한다.

todoArr을 덮어 쓰기 했으면 localStorage도 마찬가지로 덮어 쓰기 해주면 된다.

 

 

4. 새로고침시 localStorage 불러오기

const storageItems = JSON.parse(localStorage.getItem("todoItems"));

if (storageItems) {
  storageItems.forEach((e) => {
    addToDo(e);
    todoArr.push(e);
  });
}

 

마지막으로 상단에 localStorage를 불러오는 전역 변수를 설정해준다.

(JSON.parse는 string으로 저장되어 있던 것을 다시 객체로 만들어준다.)

 

만약 페이지가 렌더링 될 때 storageItems가 true라면 (= null이나 undefined가 아니라면) storageItem을 forEach를 활용해 각 요소를 addToDo 함수에 인자로 넣어 실행시키고, todoArr에도 push해준다.

 

 

 

끝.

부가적인 코드는 깃허브에서 확인 가능하다.

 

https://github.com/yangddoddi/Todo