-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[1주차] 김서연 미션 제출합니다. #3
base: main
Are you sure you want to change the base?
Changes from all commits
66c7270
09da0ec
bd8136d
42ea0b9
a97c395
67fdb6f
8e0af66
a2a0349
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,60 +0,0 @@ | ||
# 1주차 미션: Vanilla-Todo | ||
|
||
# 서론 | ||
|
||
안녕하세요 🙌🏻 21기 프론트엔드 운영진 **윤영준**입니다. | ||
|
||
이번 미션은 개발 환경 구축과 스터디 진행 방식에 익숙해지실 수 있도록 간단한 **to-do list** 만들기를 진행합니다. 무작정 첫 스터디부터 React를 다루는 것보다는 왜 React가 필요한지, React가 없으면 무엇이 불편한지 느껴 보고 본격적인 스터디에 들어가는 것이 React를 이해하는 데 더 많은 도움이 될 것이라 생각합니다. | ||
|
||
비교적 가벼운 미션인 만큼 코드를 짜는 데 있어 여러분의 **창의성**을 충분히 발휘해 보시기 바랍니다. 작동하기만 하면 되는 것보다 같은 코드를 짜는 여러가지 방식과 패턴에 대해 고민해 보시고, 본인이 생각한 가장 창의적인 방법으로 코드를 작성해 주세요. 여러분이 미션을 수행하는 과정에서 겪는 고민과 생각의 깊이만큼 스터디에서 더 많은 것을 얻어가실 수 있을 것입니다. | ||
|
||
막히는 부분이 있더라도 우선은 스스로 공부하고 찾아보는 방법을 권고드리지만, 운영진의 도움이 필요하시다면 얼마든지 프론트엔드 카톡방에 편하게 질문을 남겨 주세요! | ||
|
||
# 미션 | ||
|
||
## 미션 목표 | ||
|
||
- VSCode, Prettier를 이용하여 개발 환경을 관리합니다. | ||
- HTML/CSS의 기초를 이해합니다. | ||
- JavaScript를 이용한 DOM 조작을 이해합니다. | ||
- Vanilla Js를 이용한 어플리케이션 상태 관리 방법을 이해합니다. | ||
|
||
## 기한 | ||
|
||
- 2025년 3월 15일 토요일 23:59까지 | ||
|
||
## Key Questions | ||
|
||
- DOM은 무엇인가요? | ||
- 이벤트 흐름 제어(버블링 & 캡처링)이 무엇인가요? | ||
- 클로저와 스코프가 무엇인가요? | ||
|
||
## 필수 요건 | ||
|
||
- [결과 화면](https://vanilla-todo-19th-dh.vercel.app/)의 기능을 구현합니다. (날짜, 요일별 todo 개수) | ||
- 결과 링크의 화면 디자인 그대로 구현해도 좋고, 자신만의 디자인을 적용해도 좋습니다. | ||
- CSS의 Flexbox를 이용하여 레이아웃을 구성합니다. | ||
- JQuery, React, Bootstrap 등 외부 라이브러리를 사용하지 않습니다. | ||
- 함수와 변수의 이름은 lowerCamelCase로 짓습니다. | ||
- 코딩의 단위를 기능별로 나누어 Commit 메세지를 작성합니다. | ||
- Semantic tag를 활용하여 HTML 구조를 완성합니다. | ||
|
||
## 선택 요건 | ||
|
||
- 외부 폰트 Pretendard를 적용합니다. | ||
- 브라우저의 `localStorage` 혹은 `sessionStorage`를 이용하여 다음 번 접속 시에 기존의 투두 데이터를 불러옵니다. | ||
- 이 외에도 추가하고 싶은 기능이 있다면 마음껏 추가하셔도 됩니다. | ||
- 미디어쿼리를 이용해서 반응형을 적용합니다. | ||
|
||
# 링크 및 참고자료 | ||
|
||
- [HTML/CSS 기초](https://heropy.blog/2019/04/24/html-css-starter/) | ||
- [HTML 태그](https://heropy.blog/2019/05/26/html-elements/) | ||
- [FlexBox 가이드](https://heropy.blog/2018/11/24/css-flexible-box/) | ||
- [JS를 통한 DOM 조작](https://velog.io/@bining/javascript-DOM-%EC%A1%B0%EC%9E%91%ED%95%98%EA%B8%B0#append) | ||
- [localStorage, sessionStorage](https://www.daleseo.com/js-web-storage/) | ||
- [git 사용법](https://wayhome25.github.io/git/2017/07/08/git-first-pull-request-story/) | ||
- [좋은 코드리뷰 방법](https://tech.kakao.com/2022/03/17/2022-newkrew-onboarding-codereview/) | ||
- [MDN 공식문서-createElement()](https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement) | ||
- [MDN 공식문서-appendChild()](https://developer.mozilla.org/ko/docs/Web/API/Node/appendChild) | ||
- [DOM 개념,HTML 요소 조작](https://poiemaweb.com/js-dom#3-dom-query--traversing-%EC%9A%94%EC%86%8C%EC%97%90%EC%9D%98-%EC%A0%91%EA%B7%BC) | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script> | ||
<title>Todo-List</title> | ||
<meta charset="utf-8" /> | ||
<link rel="preconnect" href="https://fonts.googleapis.com" /> | ||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> | ||
<link | ||
href="https://fonts.googleapis.com/css2?family=Jua&display=swap" | ||
rel="stylesheet" | ||
/> | ||
<link | ||
href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR&display=swap" | ||
rel="stylesheet" | ||
/> | ||
<link rel="stylesheet" href="style.css" /> | ||
</head> | ||
|
||
<body> | ||
<div class="todo-header"> | ||
<h1 class="titles">To-do List</h1> | ||
<div id="todayInfo"></div> | ||
</div> | ||
<div class="todo-container"> | ||
<div id="inputField"> | ||
<input | ||
type="text" | ||
id="todoInput" | ||
placeholder="오늘의 할 일 입력" | ||
onkeydown="keyCodeCheck();" | ||
/> | ||
<button type="button" id="addButton" onclick="makeTodoList();"> | ||
+ | ||
</button> | ||
<div id="currentStatus">❎ 0 ✅ 0</div> | ||
</div> | ||
<div id="grid"> | ||
<div class="todo-field"> | ||
<h3 class="titles">미완료 할 일</h3> | ||
<ul id="todoList"></ul> | ||
</div> | ||
<div class="todo-field"> | ||
<h3 class="titles">완료된 할 일</h3> | ||
<ul id="completedList"></ul> | ||
</div> | ||
</div> | ||
</div> | ||
<script src="script.js"></script> | ||
</body> | ||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
// HTML이 로드되면 할 일 불러오기 | ||
document.addEventListener("DOMContentLoaded", () => { | ||
loadTodos(); | ||
loadTodayInfo(); | ||
}); | ||
|
||
// Enter 키 입력 감지 | ||
function keyCodeCheck(event) { | ||
if ( | ||
window.event.keyCode === 13 && | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. window.event가 아닌 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 처음에 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 말씀해 주신 대로 |
||
document.querySelector("#todoInput").value.trim() !== "" | ||
) { | ||
makeTodoList(); | ||
} | ||
} | ||
|
||
// 새로운 할 일 생성 함수 / 저장 데이터 불러오는 용 인자 | ||
function makeTodoList(text = "", isChecked = false) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 새 할 일을 생성한다는 의미에서 |
||
const todoInput = document.querySelector("#todoInput"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 자주 쓰이는 DOM요소들은 함수 바깥에 전역 스코프로 한번만 가져오고, 참조하면 좋을 것 같아요! |
||
const inputValue = text || todoInput.value.trim(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 사용자 입력값을 js 내부에서가 아닌 함수의 인자로 받아서 텍스트를 처리할 수도 있을 것 같아요 |
||
|
||
if (inputValue === "") return; | ||
|
||
// 새로운 할 일 요소 생성 | ||
const newLi = createTodoElement(inputValue, isChecked); | ||
|
||
// 해야 할 일 목록에 추가 | ||
updateTodoList(newLi, isChecked); | ||
|
||
if (!text) todoInput.value = ""; // 직접 입력한 경우 입력창 초기화 | ||
saveTodos(); | ||
} | ||
|
||
// 개별 할 일 요소 생성 | ||
function createTodoElement(text, isChecked) { | ||
// 리스트 생성 | ||
const newLi = document.createElement("li"); | ||
|
||
// 체크박스 생성 | ||
const checkbox = document.createElement("input"); | ||
checkbox.type = "checkbox"; | ||
checkbox.checked = isChecked; | ||
|
||
// 체크박스 이벤트 | ||
checkbox.addEventListener("change", function () { | ||
if (this.checked) { | ||
newSpan.classList.add("checkedTodos"); | ||
} else { | ||
newSpan.classList.remove("checkedTodos"); | ||
} | ||
updateTodoList(newLi, this.checked); | ||
saveTodos(); | ||
}); | ||
|
||
// 할 일 내용 | ||
const newSpan = document.createElement("span"); | ||
newSpan.textContent = text; | ||
|
||
// 저장된 데이터 체크박스 활성화 | ||
if (isChecked) newSpan.classList.add("checkedTodos"); | ||
|
||
// 삭제 버튼 | ||
const deleteButton = document.createElement("button"); | ||
deleteButton.textContent = "del"; | ||
deleteButton.classList.add("delete-button"); | ||
deleteButton.addEventListener("click", function () { | ||
newLi.remove(); | ||
saveTodos(); | ||
updateStatus(); | ||
}); | ||
|
||
newLi.append(checkbox, newSpan, deleteButton); | ||
return newLi; | ||
} | ||
|
||
// 미완료/완료 할 일 개수 업데이트 | ||
function updateStatus() { | ||
const todoList = document.querySelector("#todoList"); | ||
const completedList = document.querySelector("#completedList"); | ||
const currentStatus = document.querySelector("#currentStatus"); | ||
|
||
currentStatus.innerHTML = `❎ ${todoList.childElementCount} ✅ ${completedList.childElementCount}`; | ||
} | ||
|
||
// 미완료/완료 할 일 분리 | ||
function updateTodoList(liElement, isChecked) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 완료목록, 미완료목록을 DOM 말고 배열로 관리해도 좋습니다. 지금도 문제는 없지만 배열로 관리했을 때 로컬스토리지 저장/로딩에 더 용이할 수 있을 것 같아요! |
||
const todoList = document.querySelector("#todoList"); | ||
const completedList = document.querySelector("#completedList"); | ||
|
||
if (isChecked) { | ||
completedList.appendChild(liElement); | ||
} else { | ||
todoList.appendChild(liElement); | ||
} | ||
|
||
updateStatus(); | ||
} | ||
|
||
// localStorage 저장 함수 | ||
function saveTodos() { | ||
const todos = []; | ||
|
||
document.querySelectorAll("#todoList li, #completedList li").forEach((li) => { | ||
const text = li.querySelector("span").textContent; | ||
const isChecked = li.querySelector('input[type="checkbox"]').checked; | ||
todos.push({ text, isChecked }); | ||
}); | ||
|
||
localStorage.setItem("todos", JSON.stringify(todos)); | ||
} | ||
|
||
// localStorage에서 데이터 불러오기 | ||
function loadTodos() { | ||
const todoList = document.querySelector("#todoList"); | ||
const completedList = document.querySelector("#completedList"); | ||
|
||
todoList.innerHTML = ""; | ||
completedList.innerHTML = ""; | ||
|
||
const savedTodos = JSON.parse(localStorage.getItem("todos")) || []; | ||
savedTodos.forEach((todo) => { | ||
const newLi = createTodoElement(todo.text, todo.isChecked); | ||
updateTodoList(newLi, todo.isChecked); | ||
}); | ||
} | ||
Comment on lines
+120
to
+125
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 지금 각 li를 만들 때마다 updateTodoList로 DOM을 재조작하는데 투두리스트가 많아지면 DOM 변경이 많아져서 성능적으로 불편함이 있을 것 같아요. 전역으로 let todos =[] 배열로 두고, 할 일 추가/수정으로 배열이 변경되었을 때 renderTodos() 이런 느낌으로 한번에 DOM을 리렌더링하는 설계도 고려해보면 좋을 것 같습니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 확실히 리스트가 많아지면 말씀해 주신 방법대로 배열로 관리하는 게 성능 면에서 더 효율적일 것 같네요! 한 번 어떻게 적용해 보면 좋을지 고민해 봐야 할 것 같아요 좋은 피드백 감사합니다😊 |
||
|
||
// 오늘의 날짜 업데이트 | ||
function loadTodayInfo() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 처음에는 로컬스토리지에서 데이터를 불러오는 함수가 이런 관점도 있구나! 고민해 보시면 좋을 것 같습니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 확실히 함수 쓰임에 따라 일관적으로 함수명을 작성하는 게 가독성 면에서 훨씬 좋겠네요!! 함수명, 변수명에 대해 조금 더 고민해 볼 필요가 있을 것 같아요. 좋은 피드백 감사합니다! 😊 |
||
const todayInfo = document.querySelector("#todayInfo"); | ||
|
||
var today = new Date(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. var은 의도치 않는 스코프 이슈가 있을 수 있으니 let, const활용을 추천드립니다! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 스코프에 대해 제대로 이해하지 못한 상태로 코드를 짜기 시작해서 별다른 기준 없이 변수를 선언했던 것 같아요.💦 이번 과제 공부하면서 조금 더 잘 이해하게 되었으니 말씀해 주신 대로 적용하고 다른 부분도 다시 한 번 되짚어 봐야 할 것 같아요. 감사합니다! |
||
var year = today.getFullYear(); | ||
var month = today.getMonth(); | ||
var date = today.getDate(); | ||
var day = today.getDay(); | ||
|
||
var days = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"]; | ||
|
||
todayInfo.innerHTML = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
year + "년 " + (month + 1) + "월 " + date + "일 (" + days[day] + ")"; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
class, id를 사용하는 목적이 눈에 보이면 좋을 것 같아요. 저는 우선 class를 활용하고, 중복된 class명에서 따로 관리가 필요할 때 id를 사용합니다. 추후 리액트, Next.js에서는 id를 자주 사용하지 않지만, class명이나 활용에 있어 목적을 생각하면 더 좋은 코드를 짤 수 있다고 생각합니다 👍