오랜만에 자바스크립트 복습할겸 todolist를 만들어 보기로 하였다.
요즘 2월 이후로 별다른코딩 공부를 하지 못하고 신입으로써 취업을 위한 이력서 수정, 포폴관련 작업만 하다보니 기초가 다 까먹어서 커뮤니티 활용을하지 못하면 언뜻 기억나는 수준밖에 되지 않기에....
이분의 코드를 참고하기로 하였다
[JavaScript] 투두리스트 만들기
일단 너무 오랜만이라 어떻게 실행했는지 어떤 파일에 작성하는지 조차 까먹었었는데,
리마인드 하자면
index.html이 실제 DOM 즉 화면을 출력할 파일
index.js가 index.html에서 script로 연결지어서 javascript언어만 따로 모아놓은 파일
이라고 생각하고 코드를 분석해보자면

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>To Do List</title>
</head>
<body>
<div class="todo-container">
<h1 class="title">To Do List</h1>
<form id="todo-form">
<input type="text" id="todo-input" placeholder="어떤 계획이 있나요?" />
<button type="submit" id="submit-btn">Add</button>
</form>
<ul id="todo-list">
<li class="todo-item">
<input type="checkbox" class="item-checkbox" />
<span class="item-title">HTML 뼈대 잡기</span>
<button class="edit-btn">Edit</button>
<button class="delete-btn">Delete</button>
</li>
</ul>
</div>
<template id="todo-template">
<li class="todo-item">
<input type="checkbox" class="item-checkbox" />
<span class="item-title"></span>
<button class="edit-btn">Edit</button>
<button class="delete-btn">Delete</button>
</li>
</template>
<script src="index.js"></script>
</body>
</html>
일단 초기 세팅 코드는 html:5로 구현하였고,
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>To Do List</title>
<body>
등등 왠만한 요소에 세팅을 완료하고 디테일한건 title 제목만 내가 정해줬다 라고 생각해주면 된다 여기서 title은 웹 탭에 적히는 글씨
div class 같은 경우 해당 태그의 이름을 설정해줘서 찾기 좋게한다고 생각하면되고, id와 같은 역할
단지 id는 딱 한개의 요소만 식별하거나 접근하기 위해 사용하고
class는 여러번 사용할 수 있는 재사용성이 있고 여러 요소를 그룹화하고 스타일을 지정하거나 한꺼번에 JS를 조작할 때 사용한다고 차이를 생각하면 된다
<form id="todo-form">
form은 이벤트를 실행할 수 있는 태그기 때문에
그 안에 input과 button을 담아서 이벤트 처리가 가능하게 해줌
<input type="text" id="todo-input" placeholder="어떤 계획이 있나요?" />
input은 입력할 수 있는 태그
placeholder는 입력 필드가 비어있으면 표시되는 내부 글씨
<button type="submit" id="submit-btn">Add</button>
button은 말 그대로 버튼
type을 submit으로 설정함으로 써 폼을 제출 시킬 수 있음
span과 div차이
span은 인라인 요소, 텍스트와 다른 인라인 요소들과 한 줄에 나란히 배치, 자체는 아무런 의미가 없음 주로 텍스트의 일부분을 스타일링하거나 스크립트로 조작하기 위해 사용
div는 블록 요소, 블록 요소는 줄바꿈을 일으키며, 다른 블록 요소들과는 수직으로 쌓임
<template id="todo-template">
template는 클라이언트 쪽에서 렌더링 되지 않는 HTML콘텐츠의 청사진을 정의하는데 사용, 주로 Javascript를 사용하여 필요할때 동적으로 문서에 추가할 수 있는 HTML구조를 준비하는 데 유용, 즉 문서가 로드될 때 렌더링 되지 않고 js를 이용해서 문서의 다른 부분에 삽입하거나 복제할 수 있음
const todoForm = document.getElementById("todo-form");
const todoInput = document.getElementById("todo-input");
const todoList = document.getElementById("todo-list");
const todoTemplate = document.getElementById("todo-template");
const handleFormSubmit = (event) => {
event.preventDefault();
//todoInput의 값 추출
const inputValue = todoInput.value.trim();
//값 없으면 빨리 return
if (inputValue === "") return;
const todoItem = todoTemplate.content
.cloneNode(true)
.querySelector(".todo-item");
//.item-title에 inputValue를 추가
todoItem.querySelector(".item-title").textContent = inputValue;
// title이 추가된 todoitem을 todoList에 추가
todoList.appendChild(todoItem);
// input 초기화
todoInput.value = "";
};
todoForm.addEventListener("submit", handleFormSubmit);
이건 index.js 즉 js코드들 모음
일단 id로 원하는 태그만 document에서 getElementById를 통해 찾아준 값을 매번 작성하면 유지보수가 좋지 않으므로 상수에 할당해주기!!
일단 초기 기능 구현이기 때문에 한 함수에 여러기능을 한번에 담아서 구현하기(함수는 한 함수에 하나의 기능만 담는걸 목표로 해야함)
const handleFormSubmit = (event) => {
event.preventDefault();
여기서 handleFormSubmit이라는 함수에 event라는 객체를 인수로 넣어줘서 event.preventDefault()를 호출시켜 폼의 기본 제출 동작(페이지 새로고침)을 방지시킨다
여기서 event.preventDefault()를 호출하여 새로고침을 막는 이유는 기본 동작을 방지하고, 자바스크립트를 사용하여 폼 제출을 처리하기 위함.
HTML폼은 기본적으로 제출되면 페이지를 새로고침하거나 다른 페이지로 이동함. 그럴 경우 현재 페이지 상태가 사라지고, 사용자가 입력한 데이터도 사라질 수 있음.
const inputValue = todoInput.value.trim();
todoInput요소에서 값을 가져오고, trim()을 사용하여 앞 뒤 공백을 제거한 값을 inputValue 상수에 할당 시킴
if (inputValue === "") return;
그런데 inputValue에 할당된 값이 없으면 함수를 즉시 종료시켜 나머지 코드를 발생하지 않게 함
즉, 빈 할 일 항목이 추가되지 않도록 할 수 있음
const todoItem = todoTemplate.content
.cloneNode(true)
.querySelector(".todo-item");
todoTemplate요소에서 content를 이용해 템플릿의 컨텐츠만 참조하게 해준다. 'content'속성은 템플릿의 내용을 문서 조각(DocumentFragment)로 반환시켜줌.
문서 조각은 경량의 컨테이너로, DOM 트리에 연결되지 않는 노드들임, 이를 통해 여러 노드를 한 번에 삽입할 수 있음.
cloneNode(true)는 todoTemplate.content의 모든 자식 노드를 깊은 복사한다. true 매개변수는 복사할 때 모든 자식 노드와 하위 요소들을 포함한다는 의미.
결과적으로 템플릿의 모든 내용을 복사한 새로운 문서 조각을 반환시켜줌
querySelector('.todo-item')은 클론된 문서 조각에서 '.todo-item' 클래스를 가진 첫 번째 요소를 선택해줌.
이 선택자는 복사된 템플릿 내용 중에서 특정 요소를 찾기 위해 사용
todo-item이 아닌 .todo-item을 넣은 이유는 CSS 선택자의 사용 여부에 있음. querySelector 메서드는 CSS 선택자를 사용하여 DOM 요소를 선택하기 때문에 class를 나타내기 위해서 .를 앞에 붙여야함.
즉 .가 클래스 선택자를 나타내는 기호이기 때문에,
.todo-item은 클래스 이름이 todo-item인 요소를 선택하게 만들어줌
todoItem.querySelector(".item-title").textContent = inputValue;
todoItem 요소 내부에서 클래스가 item-title인 첫 번째 요소를 찾아서 그 요소의 텍스트 내용을 inputValue로 설정해줌. 이를 통해 사용자가 입력한 텍스트가 템플릿에 추가됨
todoList.appendChild(todoItem);
todoItem요소를 todoList요소의 자식으로 추가함.
appendChild는 DOM API의 메서드로, 선택한 요소의 마지막 자식으로 새로운 자식 노드를 추가함
즉 todoList의 자식 노드 목록의 마지막에 todoItem을 추가하게 됨
이 메서드는 추가된 자식 노드를 반환
todoInput.value = "";
업데이트된 todoItem을 todoList요소에 자식으로 추가한 이후 todoInput의 요소의 값을 빈 문자열로 설정하여 입력 필드 자체를 초기화.
즉 생성 후 인풋 내용 초기화 시킨다고 보면 된다.
todoForm.addEventListener("submit", handleFormSubmit);
todoForm요소어 submit 즉 제출 이벤트 리스너를 추가한 후, submit 할때마다 handleFormSubmit함수가 호출되도록 세팅
이렇게 초기 기능을 위한 코드작성을 완료하였으나, 실제로 제출버튼을 눌러도 새로고침만 반복되는 상황이 펼쳐졌는데,
확인해보니 form에 id값을 넣어주지 못한 휴먼이슈가 있었다. 참 예민하다 코딩은..!
자 그럼 이제 리펙토링 과정을 거쳐 함수를 하나하나 나눠줘야하는데
1. 탬플릿 태그 활용 가상의 .todo-item을 만드는 역할(생성)
2. .todo-item을 todoList에 넣어주는 역할(추가)
3. .todo-item을 추가하고 나서 todoInput초기화 해주는 역할(인풋 초기화)
//생성
const createTodoItem = (itemTitle) => {
const todoItem = todoTemplate.content
.cloneNode(true)
.querySelector(".todo-item");
todoItem.querySelector(".item-title").textContent = itemTitle;
return todoItem;
};
//추가
const addTodoItem = (itemTitle) => {
const todoItem = createTodoItem(itemTitle);
todoList.appendChild(todoItem);
};
//인풋 초기화
const clearTodoInput = () => {
todoInput.value = "";
};
const handleFormSubmit = (event) => {
event.preventDefault();
const inputValue = todoInput.value.trim();
if (inputValue === "") return;
addTodoItem(inputValue);
clearTodoInput();
};
todoForm.addEventListener("submit", handleFormSubmit);
이렇게 로직을 나눠어서 handleFormSubmit에 넣어줬는데,
// 생성
const createTodoItem = (itemTitle) => {
const todoItem = todoTemplate.content
.cloneNode(true)
.querySelector('.todo-item');
todoItem.querySelector('.item-title').textContent = itemTitle;
return todoItem;
};
생성같은 경우 인자로 받아온 즉, form에서 submit받은 input 값을 itemTitle의 매개변수명으로 설정해놓고 인수로 받아서 createTodoItem이라는 화살표 함수로 정의
todoItem은 todoTemplate 요소의 내용은 깊은 복사한 후, 복사된 내용에서 첫번째로 발견되는 클래스가 .todo-item에 할당
그 후 todoItem요소 내부에서 클래스가 .item-title인 첫 번째 요소를 찾아 그 요소의 텍스트 내용을 itemTitle로 설정. 이러면 사용자가 입력한 텍스트를 템플릿에 추가할 수 있음
그 다음 todoItem 요소를 반환하고 함수 종료
//추가
const addTodoItem = (itemTitle) => {
const todoItem = createTodoItem(itemTitle);
todoList.appendChild(todoItem);
};
input에 넣어놓은 값을 itemTitle이라는 인수로 받아 새로운 할 일 항목을 리스트에 추가하는 함수
동일한 itemTitle을 createTodoItem(itemTitle)에서 생성된 데이터 값을 todoItem에 넣기
todoItem을 todoList의 요소의 자식으로 추가 시킴. 이걸 함으로써 페이지에 표시시킬 수 있음
//인풋 초기화
const clearTodoInput = () => {
todoInput.value = "";
};
form에서 사용한 input값을 초기화 시킴
이 3개의 함수를 handleFormSubmit에 넣어줘서 초기 코드보단 간소화 시킴
로컬스토리지 활용해서 저장 로직을 만들 것.
불러오는 환경 구축해보기.
여기서 로컬스코리지는 객체처럼 key:value 쌍으로 값을 저장하고 불러올 수 있는 구조로 되어 있음
여러 개의 .todo-item들의 정보를 저장해야 하니 배열의 형태로 아이템들을 다루고, 배열을 todos라는 키로 관리
const STORAGE_KEY = "todos";
const getTodos = () => {
return JSON.parse(localStorage.getItem(STORAGE_KEY)) || [];
};
이렇게 로직을 짜면 로컬스토리지의 getItem 메서드로 todos에 접근해서 값이 있으면?? JSON으로 파싱해서 리턴시켜주고, 없으면 빈 리턴을 리턴해주는 것까지 고려해주는 함수
여기서 키 값은 불러올 때, 저장할 때, 삭제할 때 곳곳에 쓰이므로 STORAGE_KEY로 따로 선언시키면 유지 보수에 용이
const saveTodoItem = (todoItem) => {
const todoData = {
title: todoItem.querySelector(".item-title").textContent,
};
const todos = getTodos().concat(todoData);
localStorage.setItem(STORAGE_KEY, JSON.stringify(todos));
};
실질적으로 로컬스토리지에 값을 저장시키는 함수
todoItem에서 클래스가 .todo-item인 요소를 찾은 후 그 요소의 텍스트 내용만 추출한걸 title속성으로 가지는 객체 todoData 안에 넣어주기
getTodos()함수를 호출하면 기존의 할 일 목록을 가져오는데, 이 함수가 반환하는 값에 todoData 객체를 추가한 새로운 배열 todos를 생성하는데 여기서 concat메소드는 기존 배열을 변경하지 않고 새로운 배열을 반환해주는 역할
결국 todos배열을 JSON 문자열로 변환 후, STORAGE_KEY를 이용하여 로컬 스토리지에 저장.
여기서 localStorage.setItem메소드는 키와 값을 문자열로 저장시켜줌
여기서 todoItem들의 title만 감싸는 배열을 만들지 않은건, 체크 상태도 함께 관리할 예정이기 때문
즉, todoData에 프로퍼티를 더 추가할 예정
마지막으로 addTodoItem쪽에서
//추가
const addTodoItem = (itemTitle) => {
const todoItem = createTodoItem(itemTitle);
todoList.appendChild(todoItem);
// 여기서 생성한 아이템을 리턴(로컬스토리지할때 추가함)
return todoItem;
};
const handleFormSubmit = (event) => {
event.preventDefault();
const inputValue = todoInput.value.trim();
if (inputValue === "") return;
//로컬스토리지 추가하면서 saveTodoItem추가
saveTodoItem(addTodoItem(inputValue));
clearTodoInput();
};
return문을 추가해줬는데, 이렇게 수정하면 addTodoItem에서 추가된 아이템들이 그대로 saveTodoItem의 아규먼트로 전달되면서 화면에 아이템을 추가하고 저장하는 순서로 코드가 동작하게됨
이젠

로컬에까지 정상적으로 잘 추가가되는 모습
그러나 새로고침하면 로컬에는 값이 저장되도 페이지출력에선 사라짐
결국 상태가 로컬과 화면이 차이가 발생하게 됨
그렇기 때문에 페이지 새로고침 했을 때, 로컬스토리지에 저장된 값을 불러와서 화면에 그려주는 것 까지 해야함
const loadTodoItem = () => {
getTodos().foreach((todo) => addTodoItem(todo.title));
};
getTodos로 저장된 아이템들을 불러오고 forEach메서드로 각 아이템들의 title로 addTodoItem함수를 실행해주는 내용
이제 load 함수를 실행시키기 위해 submit이벤트 등록하는 부분 근처에서 실행할 예정
그 전에 페이지가 로드되었을 때 일어나야 할 작업들을 한 군데 모아주는 작업을 하려함
첫 페이지에 접근했을 때
1. todoForm에 submit 이벤트 등록
2. loadTodoItem 함수 실행
여기에 clearTodoInput 함수실행까지 총 3가지 일을 수행하는 init 함수를 제작함
// todoForm.addEventListener("submit", handleFormSubmit);
const initTodoApp = () => {
todoForm.addEventListener("submit", handleFormSubmit);
loadTodoItem();
clearTodoInput();
};
기존 따로 실행하던 addEventListener는 삭제
document.addEventListener("DOMContentLoaded", initTodoApp);
페이지의 콘텐츠가 완전히 로드된 후 실행되도록하기 위한 코드
이 코드를 구성한 이유는 initTodoApp 함수로 페이지가 완전히 로드된 다음에 반드시 필요한 행동들을 추상화 하기 위함,
DOMContentLoaded 이벤트의 핸들러로 등록함으로써 페이지가 완전히 로드된 다음이라는 시점을 명확히 하기 위해서임
이제 지금까지 클론코딩을 진행했다면 삭제 수정 체크박스를 로컬스토리지에 담는 것도 진행해볼 예정인데
코드를 하나 하나 분석해보겠다
일단 전체 코드
const todoForm = document.getElementById("todo-form");
const todoInput = document.getElementById("todo-input");
const todoList = document.getElementById("todo-list");
const todoTemplate = document.getElementById("todo-template");
const STORAGE_KEY = "todos";
const getTodos = () => {
return JSON.parse(localStorage.getItem(STORAGE_KEY)) || [];
};
//마지막으로 추가
const saveTodos = (todos) => {
localStorage.setItem(STORAGE_KEY, JSON.stringify(todos));
};
const saveTodoItem = (todoItem) => {
const todoData = {
title: todoItem.querySelector(".item-title").textContent,
//체크박스 추가
completed: todoItem.querySelector(".item-checkbox").checked,
};
const todos = getTodos().concat(todoData);
// //이 부분을 왜 나눴을지 확인하기
// localStorage.setItem(STORAGE_KEY, JSON.stringify(todos));
saveTodos(todos);
};
//업데이트
const updateTodoItem = (oldTitle, newTitle, completed) => {
const todos = getTodos().map((todo) =>
todo.title === oldTitle ? { title: newTitle, completed } : todo
);
saveTodos(todos);
};
//삭제
const deleteTodoItem = (title) => {
const todos = getTodos().filter((todo) => todo.title !== title);
saveTodos(todos);
};
//생성
const createTodoItem = (itemTitle, completed = false) => {
const todoItem = todoTemplate.content
.cloneNode(true)
.querySelector(".todo-item");
//새로 추가
const checkbox = todoItem.querySelector(".item-checkbox");
const title = todoItem.querySelector(".item-title");
const editBtn = todoItem.querySelector(".edit-btn");
const deleteBtn = todoItem.querySelector(".delete-btn");
title.textContent = itemTitle;
checkbox.checked = completed;
checkbox.addEventListener("change", () => {
updateTodoItem(itemTitle, itemTitle, checkbox.checked);
});
editBtn.addEventListener("click", () => {
const newTitle = prompt("Edit your task:", itemTitle);
if (newTitle) {
updateTodoItem(itemTitle, newTitle, checkbox.checked);
title.textContent = newTitle;
itemTitle = newTitle;
}
});
deleteBtn.addEventListener("click", () => {
todoItem.remove();
deleteTodoItem(itemTitle);
});
// //위에서 사용
// todoItem.querySelector(".item-title").textContent = itemTitle;
return todoItem;
};
//추가
const addTodoItem = (itemTitle) => {
const todoItem = createTodoItem(itemTitle);
todoList.appendChild(todoItem);
saveTodoItem(todoItem);
// // 여기서 생성한 아이템을 리턴(로컬스토리지할때 추가함)
// return todoItem;
};
// 최종에서 todo.completed추가됨
const loadTodoItem = () => {
getTodos().forEach((todo) => addTodoItem(todo.title, todo.completed));
};
//인풋 초기화
const clearTodoInput = () => {
todoInput.value = "";
};
const handleFormSubmit = (event) => {
event.preventDefault();
const inputValue = todoInput.value.trim();
if (inputValue === "") return;
//로컬스토리지 추가하면서 saveTodoItem추가
saveTodoItem(addTodoItem(inputValue));
clearTodoInput();
};
// todoForm.addEventListener("submit", handleFormSubmit);
const initTodoApp = () => {
todoForm.addEventListener("submit", handleFormSubmit);
loadTodoItem();
clearTodoInput();
};
document.addEventListener("DOMContentLoaded", initTodoApp);
const saveTodos = (todos) => {
localStorage.setItem(STORAGE_KEY, JSON.stringify(todos));
};
saveTodoItem에 있던 코드지만 삭제 및 업데이트에서도 사용되므로 로컬 스토리지에 저장하는 함수를 따로 뺌
const saveTodoItem = (todoItem) => {
const todoData = {
title: todoItem.querySelector(".item-title").textContent,
completed: todoItem.querySelector(".item-checkbox").checked,
};
const todos = getTodos().concat(todoData);
saveTodos(todos);
};
추가로 체크박스 상태까지 todoData에 담아서 기존 목록에 추가한 후 저장을 해줌으로써 saveTodos와 분간지음
여기서 saveTodoItem은 개별 할 일 항목을 로컬 스토리지에 저장하는 함수
할 일 제목과 체크박스 상태를 객체로 만들어 기존 목록에 추가한 후 저장시킴
checked는 HTML input요소에 사용되는 속성
주로 체크박스나 라디오버튼과 같은 입력 요소에서 사용되며, 선택 여부를 나타냄
이 값은 추후 completed라는 속성에 저장되어, 나중에 이 할일이 완료되었는지를 나타내는데 사용
const updateTodoItem = (oldTitle, newTitle, completed) => {
const todos = getTodos().map((todo) =>
todo.title === oldTitle ? { title: newTitle, completed } : todo
);
saveTodos(todos);
};
update시켜주는 함수
제목이 oldTitle인 항목을 찾아 newTitle로 그리고 completed 상태로 업데이트 시켜줌
일단 getTodos 함수 호출을 통해 기존 할일 목록을 다 가져오고
map시키면서 각 할일 제목이 oldTitle요소와 같은지 비교하고 같으면 제목을 newTitle로 완료 상태를 completed로 변경시킨다.
그 값이 다르면 기존 할일 목록 그대로 반환 시킨다
그 모든 결과값을 saveTodos에 요소로 넣어줘서 저장시킴
const deleteTodoItem = (title) => {
const todos = getTodos().filter((todo) => todo.title !== title);
saveTodos(todos);
};
할 일을 삭제시키는 함수. 제목이 title인 항목을 filter처리해서 없애고 저장
const createTodoItem = (itemTitle, completed = false) => {
const todoItem = todoTemplate.content
.cloneNode(true)
.querySelector(".todo-item");
const checkbox = todoItem.querySelector(".item-checkbox");
const title = todoItem.querySelector(".item-title");
const editBtn = todoItem.querySelector(".edit-btn");
const deleteBtn = todoItem.querySelector(".delete-btn");
title.textContent = itemTitle;
checkbox.checked = completed;
checkbox.addEventListener("change", () => {
updateTodoItem(itemTitle, itemTitle, checkbox.checked);
});
editBtn.addEventListener("click", () => {
const newTitle = prompt("Edit your task:", itemTitle);
if (newTitle) {
updateTodoItem(itemTitle, newTitle, checkbox.checked);
title.textContent = newTitle;
itemTitle = newTitle;
}
});
deleteBtn.addEventListener("click", () => {
todoItem.remove();
deleteTodoItem(itemTitle);
});
return todoItem;
};
새 할일을 생성하는 함수에 추가된 내용들이 있는데,
itemTitle - 할 일 제목을 매개변수로 받음
completed - 완료 여부를 나타내는 매개변수 - 기본값은 false
todoTemplate - 복제해서 새로운 할 일을 만듬
할 일 제목을 설정하고 체크박스 상태 설정(체크하면 true - completed)
체크박스 변경 시 updateTodoItem함수 호출시켜서 상태 업데이트 시켜줌
편집 버튼 클릭 시 제목을 수정할 수 있는 prompt를 띄워 새로운 제목으로 업데이트
삭제 버튼 클릭 시 할일을 삭제시킴
//추가
const addTodoItem = (itemTitle) => {
const todoItem = createTodoItem(itemTitle);
todoList.appendChild(todoItem);
saveTodoItem(todoItem);
// // 여기서 생성한 아이템을 리턴(로컬스토리지할때 추가함)
// return todoItem;
};
creteTodoItem 함수 호출하여 새로운 할일 생성
creteTodoItem 함수는 제목을 기반으로 DOM 요소 생성하여 반환
saveTodoItem 함수를 호출하여 todoItem을 저장
이 함수로 인해 할일을 로컬 스토리지에 저장할 수 있음
최초엔 return todoItem 넣었지만 뺀 이유는
이미 saveTodoItem으로 값을 넣어주기 때문
const loadTodoItems = () => {
getTodos().forEach((todo) => addTodoItem(todo.title, todo.completed));
};
로컬 스토리지에 저장된 할 일 목록을 불러와서 화면에 표시하는데 나머지는 똑같으니 completed만 추가된것만 체크하면 됨
결국 리펙토링에 리펙토링을 거쳐 완성!!

이건 틈틈히 반복 숙달을 통해 이 코드를 익혀야 안보고 구현 할 수 있을 것 같다...
