Ted's Codding study

로컬 스토리지 Todo 예제 - 정답 본문

JavaScript

로컬 스토리지 Todo 예제 - 정답

Ted93 2024. 6. 2. 22:00

로컬 스토리지를 활용한 Todo 예제 정답입니다.

절대 제가 작성한 코드가 무조건 적인 정답은 아니며, 여러분의 생각과 다르다면 그것이 정답일 것입니다~~

HTML 코드
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Todo List Application</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="container">
        <input type="text" id="todoInput" placeholder="Add new task..." aria-label="New task input">
        <button onclick="addTodo()">Add Task</button>
        <ul id="todoList"></ul>
    </div>
    <script src="main.js"></script>
</body>
</html>
CSS 코드
body {
  background-color: #f4f4f4;
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
  margin: 0;
}

.container {
  background: white;
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1);
  width: 500px;
}

input[type='text'] {
  width: calc(100% - 22px);
  padding: 10px;
  margin-bottom: 10px;
  border: 2px solid #ddd;
  border-radius: 4px;
}

button {
  padding: 10px;
  background-color: #5cb85c;
  border: none;
  border-radius: 4px;
  color: white;
  font-size: 16px;
  cursor: pointer;
}

button:hover {
  background-color: #4cae4c;
}

ul {
  list-style: none;
  padding: 0;
}

li {
  background: #eee;
  margin-top: 8px;
  padding: 10px;
  border-radius: 4px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

button.deleteBtn {
  background-color: #d9534f;
  border: none;
  border-radius: 4px;
  padding: 5px 10px;
  color: white;
  cursor: pointer;
  width: 80px;
  min-width: 60px;
}

button.deleteBtn:hover {
  background-color: #c9302c;
}

#error {
  color: red;
  text-align: center;
}
.hidden {
  visibility: hidden;
}
전체 JS 코드
let todos = [];

document.addEventListener('DOMContentLoaded', () => {
  todos = JSON.parse(localStorage.getItem('todos')) || [];
  renderTodos(todos);
});

function addTodo() {
  const input = document.getElementById('todoInput');
  const newTodo = input.value.trim();
  if (newTodo) {
    todos.push(newTodo);
    localStorage.setItem('todos', JSON.stringify(todos));
    renderTodos(todos);
    input.value = '';
  }
}

function renderTodos() {
  const list = document.getElementById('todoList');
  list.innerHTML = '';

  todos.forEach((todo, index) => {
    const li = document.createElement('li');
    li.textContent = todo;

    const deleteBtn = document.createElement('button');
    deleteBtn.textContent = 'Delete';
    deleteBtn.onclick = () => removeTodo(index);

    li.appendChild(deleteBtn);
    list.appendChild(li);
  });
}

function removeTodo(index) {
  todos.splice(index, 1);
  localStorage.setItem('todos', JSON.stringify(todos));
  renderTodos(todos);
}

1. 할 일 목록을 저장할 빈 배열을 선언

let todos = [];

 

2. DOMContentLoaded 이벤트가 발생하면, 즉 문서 로딩이 완료되면 함수들을 실행

☝ 로직
1. 기존에 todos에서 로컬스토리지에 저장된 값이 있다면 꺼내오고 없다면 빈 배열을 할당
2. 렌더링 함수를 호출 (밑에서 구현 예정)
document.addEventListener('DOMContentLoaded', () => {
  todos = JSON.parse(localStorage.getItem('todos')) || [];
  renderTodos(todos);
});

 

3. 할 일을 추가하는 함수

☝ 로직
1. addTodo 함수 구현
2. input을 가져오고, 새로운 투두에 그 인풋의 값을 넣어줌
3. 만약 새로운 투두가 있다면, 투두리스트에 추가해주고
4. 로컬 스토리지에도 추가해줌
5. 렌더링 함수를 호출하고, 인풋의 값은 비워줌
function addTodo() {
  const input = document.getElementById('todoInput');
  const newTodo = input.value.trim();
  if (newTodo) {
    todos.push(newTodo);
    localStorage.setItem('todos', JSON.stringify(todos));
    renderTodos(todos);
    input.value = '';
  }
}

 

4. 할 일 목록을 렌더링하는 함수

☝ 로직
1. list를 가져오고, list의 HTML을 빈 값으로 할당
2. todos배열을 순회하면서, li 요소를 생성
3. li 요소의 값은 todo로 채워줌
4. delete 버튼을 생성하고 값은 Delete로 채워줌
5. delete 버튼을 클릭했을 때 removeTodo 함수를 호출
6. removeTodo 함수는 (밑에서 구현 예정)
7. li 요소의 자식으로 버튼을 추가하고
8. list의 자식으로 li를 추가
function renderTodos() {
  const list = document.getElementById('todoList');
  list.innerHTML = '';

  todos.forEach((todo, index) => {
    const li = document.createElement('li');
    li.textContent = todo;

    const deleteBtn = document.createElement('button');
    deleteBtn.textContent = 'Delete';
    deleteBtn.onclick = () => removeTodo(index);

    li.appendChild(deleteBtn);
    list.appendChild(li);
  });
}

 

5. 할 일을 삭제하는 함수

☝ 로직
1. index를 인자로 받아옴
2. 배열의 메소드를 호출해서 인자로 넣어준 index를 삭제
3. 로컬 스토리지에도 업데이트
4. 렌더링 함수를 호출
function removeTodo(index) {
  todos.splice(index, 1);
  localStorage.setItem('todos', JSON.stringify(todos));
  renderTodos(todos);
}

 

6. 다른 버전으로 리팩토링

데모 웹사이트

깃허브

기존 코드에서 달라진 부분 위주로 주석으로 설명 첨부

JS 코드
let toDos = [];

document.addEventListener('DOMContentLoaded', () => {
  toDos = JSON.parse(localStorage.getItem('toDos')) || [];
  const list = document.getElementById('todoList');
  // 버튼에 하나하나 이벤트 리스너 다는 것을 피하고
  // list 하나에만 이벤트 리스너를 설계
  list.addEventListener('click', (e) => {
    const btnId = e.target.dataset.btnId;
    console.log(btnId);
    if (btnId) {
      removeTodo(btnId);
    }
  });
  renderPage(toDos);
});

// 엔터 키로도 addTodo가 작동하도록 설계
document.addEventListener('keydown', (e) => {
  if (e.key === 'Enter') {
    addTodo();
  }
});

function addTodo() {
  const input = document.getElementById('todoInput');
  let newTodo = input.value.trim();
  const p = document.getElementById('error');
  if (newTodo) {
    const data = JSON.parse(localStorage.getItem('toDos'));
    if (data && data.includes(newTodo)) {
      // 기존에 이미 존재하는 todo가 있는지 확인
      // 존재한다면 에러 메세지를 출력해줌
      p.classList.remove('hidden');
      renderPage(toDos);
      return;
    }
    p.classList.add('hidden');
    toDos.push(newTodo);
    localStorage.setItem('toDos', JSON.stringify(toDos));
    renderPage(toDos);
    input.value = '';
  }
}

function renderPage(toDos) {
  const list = document.getElementById('todoList');
  list.innerHTML = '';
  toDos.forEach((todo, index) => {
    const li = document.createElement('li');
    li.innerText = todo;
    const deleteBtn = document.createElement('button');
    deleteBtn.innerText = 'Delete';
    // 원하는 버튼을 삭제하기 위해서 dataset을 설정
    deleteBtn.dataset.btnId = index;
    li.appendChild(deleteBtn);
    list.appendChild(li);
  });
}

// 기존 index 대신에 btnId를 인자로 전달
function removeTodo(btnId) {
  toDos.splice(btnId, 1);
  localStorage.setItem('toDos', JSON.stringify(toDos));
  renderPage(toDos);
}

 

노션으로 보고 싶다면?

https://short-echidna-b16.notion.site/Todo-0495232881534a55957e0c3e1aa9afc7?pvs=4

 

로컬 스토리지 Todo 예제 - 정답 | Notion

로컬 스토리지를 활용한 Todo 예제 정답입니다.

short-echidna-b16.notion.site