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