-
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주차] 송유선 미션 제출합니다. #8
base: master
Are you sure you want to change the base?
Conversation
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.
멋진 과제 진행하시느라 너무 고생 많으셨습니다! @keyframes
와 같은 문법으로 스타일링 라이브러리 없이도 너무 나이스한 UI를 잘 구현하신다는 생각이 먼저 들었어요 👍
기본적인 html과 javascript 동작 원리도 잘 이해하고 계신 것 같아요. 다만, 평소에 reactJS를 이용하여 개발하시다가 바닐라 JS로 웹 개발을 하다보니 다소 불편하고 신경써줘야할 것이 많다는 생각을 하셨을 것 같은데요. 이것이 이번 과제의 취지였습니다!
다른 분들 코드 리뷰도 과제처럼 정성스럽게 해주시고 스터디 때 좋은 주제로 멋진 토론 할 수 있으면 좋겠습니다. 두산 베어스 화이팅~ 🐻
@@ -4,11 +4,21 @@ | |||
<meta charset="UTF-8" /> | |||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |||
<title>Vanilla Todo</title> | |||
<link rel="stylesheet" href="style.css" /> | |||
<link rel="stylesheet" href="css/normalize.css"> |
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.
<link rel="stylesheet" href="css/normalize.css"> | |
<link href=" | |
https://cdn.jsdelivr.net/npm/[email protected]/normalize.min.css | |
" rel="stylesheet"> |
normalize.css 파일을 가져와서 잘 제공해주시는 것 같아요!
이 방식도 좋구 CDN을 활용한 link 태그로 적용해주는 방법도 알고 계시면 좋을 것 같습니다
</body> | ||
<script src="script.js"></script> | ||
<script src="script.js" defer></script> |
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.
js 파일을 담은 script 요소을 body 태그 뒤에 배치하여 DOM 트리 요소가 모두 파싱된 이후에 js 로 조작을 하게 만들어주셨네요!
제가 코드에서 defer
속성을 제거해도,,, 일단은 잘 동작하는 것 같아요! defer
속성은 js 파일의 다운로드가 백그라운드에서 진행되어 기존의 html 요소의 파싱을 block하지 않고 모든 다운이 끝나면 그때 순서에 맞게 실행이 되는 것으로 알고 있어요.
따라서 defer
속성을 붙인 script
태그를 head 태그 내부에 배치하시거나 아니면 defer를 없앤 script 태그를 body 요소 다음에 배치하는 방식 모두 괜찮다고 생각합니다.
<div id="date"></div> | ||
<div class="container"> | ||
<div class="title"> | ||
<h2>To-Do</h2> | ||
<div id="todo-count"></div> | ||
</div> | ||
<form id="todo-form"> | ||
<input type="text" placeholder="오늘 해야 할 일은?"/> | ||
</form> | ||
<ul id="todo-list"></ul> | ||
</div> |
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.
html의 구조에 대해서 너무 잘 이해하고 계신 것 같아요. 다만 좀더 semantic하게 section
, article
같은 요소를 활용하시는 것도 좋을 것 같아요👍
localStorage.setItem('todos', JSON.stringify(todos)); | ||
} | ||
|
||
// let todos = (JSON.parse(localStorage.getItem('todos') ?? [])).filter(todo => todo.date === getDate()); | ||
// function saveTodos() { | ||
// localStorage.setItem('todos', JSON.stringify(todos)) | ||
// }; <-- JSON 오류가 났던 코드입니다. 왜 이 코드로 하면 오류가 났는지 알아가는 과정에서 새로 배운 것들이 있어 코드 리뷰하시는 분도 보시라고 남겨 놓습니다. | ||
|
||
// todo 정렬 함수 (완료된 일은 밑으로 배치) | ||
function sortTodo() { | ||
todos.sort((a, b) => a.done - b.done); | ||
} | ||
|
||
// todo 렌더링 함수 | ||
function renderTodo() { | ||
todoList.innerHTML = ''; | ||
sortTodo(); | ||
todos.forEach(todo => showTodo(todo)); | ||
getTodoCount(); | ||
} | ||
|
||
// todo 항목 표시 함수 | ||
function showTodo(todo){ | ||
const li = document.createElement('li'); | ||
li.id = todo.id; | ||
|
||
// todo 내용 | ||
const span = document.createElement('span'); | ||
span.innerText = todo.text; | ||
|
||
// 완료 버튼 | ||
todo.done && span.classList.add('done'); | ||
const doneBtn = document.createElement('button'); | ||
|
||
doneBtn.innerText = todo.done === true ? '♥' : '♡'; | ||
doneBtn.onmouseenter = () => doneBtn.innerText = '♥'; | ||
doneBtn.onmouseleave = () => doneBtn.innerText = todo.done ? '♥' : '♡'; | ||
|
||
doneBtn.classList.add('doneBtn'); | ||
doneBtn.addEventListener('click', () => { | ||
span.classList.toggle('done'); | ||
todo.done = !todo.done; | ||
saveTodos(); | ||
renderTodo(); | ||
}) | ||
|
||
// 삭제 버튼 | ||
const delBtn = document.createElement('button'); | ||
delBtn.innerText = '×'; | ||
delBtn.classList.add('delBtn'); | ||
delBtn.addEventListener('click', deleteTodo) | ||
|
||
li.appendChild(doneBtn); | ||
li.appendChild(span); | ||
li.appendChild(delBtn); | ||
|
||
todoList.appendChild(li); | ||
} | ||
|
||
// 할 일 추가 함수 | ||
todoForm.addEventListener('submit', addTodo); | ||
|
||
function addTodo(e){ | ||
|
||
e.preventDefault(); | ||
const newTodo = todoInput.value; | ||
|
||
if(newTodo===''){ | ||
alert("할 일을 입력해주세요!"); | ||
} | ||
|
||
else{ | ||
const newTodoObj = { | ||
id : Date.now(), | ||
text : newTodo, | ||
done : false, | ||
date : getDate() | ||
} | ||
|
||
todos.push(newTodoObj); | ||
saveTodos(); | ||
showTodo(newTodoObj); | ||
renderTodo(); | ||
|
||
todoInput.value = ''; | ||
} | ||
}; | ||
|
||
|
||
// 할 일 삭제 함수 | ||
function deleteTodo(e){ | ||
const li = e.target.parentElement; | ||
li.remove(); | ||
|
||
todos = todos.filter(todo => todo.id !== parseInt(li.id)); | ||
saveTodos(); | ||
renderTodo(); | ||
}; | ||
|
||
renderTodo(); |
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.
코드적인 부분은 아니고, 하루 하루 해야할 일을 기록하는 todo list라면 충분하다고 생각되지만 날짜를 고를 수 있는 기능까지 추가되면 더 좋을 것 같아요.
물론 그러려면 날짜별로, item 별로 로컬 스토리지에 어떻게 저장할지와 동일한 날짜 내에서도 동일한 contents를 담은 todo item
을 어떻게 구별할지에 대한 고민도 해보시면 좋을 것 같아요
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.
js 파일의 로직과는 무관한 부분인데, .prettierrc
설정 파일에 대해서 공부해보시면 좋을 것 같아요.
제가 과제 가이드 발표에서 말씀드린 것처럼 동료 개발자와 협업할 때에는 단순한 공백, 줄 넘김 등으로 git 충돌이 발생할 수 있어 해당 설정을 해두면 vscode 단에서 이를 맞춰줄 수 있거든요.
{
"trailingComma": "es5",
"tabWidth": 2,
"semi": true,
"singleQuote": true
}
저 같은 경우는 이런 방식의 설정 파일을 프로젝트의 루트 디렉터리에 만들어두고 vscode의 prettier 관련 익스텐션을 설치하여 코드의 일관성을 높이고 있어요 👍
|
||
input { | ||
all: unset; | ||
box-sizing: border-box; |
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.
저는 개인적으로 html 요소들의 너비와 높이를 잡아줄 때 기본적으로 box-sizing 속성 값을 border-box
로 잡아주긴 해요.
이렇게 하면 width와 height를 계산할 때 margin 만을 제외한 context box와 padding, border를 포함한 수치로 계산되어 나중에 css 디버깅시에 용이하거든요!
관련 문서 참고해보시면 좋을 것 같아요 🙃
flex-direction: column; | ||
align-items: center; | ||
justify-content: center; | ||
font-family:'pretendard'; |
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.
body 요소에 font-family
속성을 적용하여 공통적으로 쓰일 폰트 스타일을 한번에 적용해주시는 모습 좋습니다. 전체는 시스템화, 예외는 적게요
|
||
// todo 렌더링 함수 | ||
function renderTodo() { | ||
todoList.innerHTML = ''; |
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.
innerHTML에 관련된 영준님께 달아드린 리뷰 한번 참고해보시면 좋을 것 같아요 :)
localStorage.setItem('todos', JSON.stringify(todos)); | ||
} | ||
|
||
// let todos = (JSON.parse(localStorage.getItem('todos') ?? [])).filter(todo => todo.date === getDate()); |
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.
음 이 코드를 제가 실제로 돌려본 것은 아닌데, JSON.parse()
함수에 []까지 묶여있어서 그런 것이 아닐까 싶어요.
// let todos = (JSON.parse(localStorage.getItem('todos') ?? [])).filter(todo => todo.date === getDate()); | |
let todos = localStorage.getItem('todos') ? JSON.parse(localStorage.getItem('todos')) ? [].filter(todo => todo.date === getDate()); |
삼항 연산자를 활용하여 위와 같은 방식으로도 처리할 수 있지 않을까 싶어요.
`JSON.parse()` 메서드의 인터페이스를 까보니, 인자로는 문자열이 오는 것이 맞아서, 오히려 '[]' 처럼 표현해줬으면 js 엔진이 알아서 배열로 파싱해줬을 수 있겠네욥 🙃todo.done && span.classList.add('done'); | ||
const doneBtn = document.createElement('button'); | ||
|
||
doneBtn.innerText = todo.done === true ? '♥' : '♡'; |
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.
오 오히려 이런 이미지를 SVG로 따워 추가적인 리소스를 발생시키는 것보다 유선님처럼 text 형식으로 3항 연산자를 적용하되, color 속성과 text-shadow 속성으로 스타일링 하는 것도 참 좋은 방법이네요 :)
function getDate() { | ||
const today = new Date(); | ||
const year = today.getFullYear(); | ||
const month = today.getMonth() + 1; | ||
const day = today.getDate(); | ||
return `⊹ ⋆ ${year}. ${month}. ${day}. ⋆ ⊹` | ||
} | ||
date.innerText = getDate(); | ||
|
||
// 개수 함수 | ||
function getTodoCount() { | ||
totalTodo = todos.length; | ||
doneTodo = todos.filter(todo => todo.done).length; | ||
todoCount.innerText = `${doneTodo} / ${totalTodo}`; | ||
} | ||
|
||
// 로컬 스토리지에 저장하기, 불러오기 | ||
let todos = []; | ||
|
||
try { | ||
todos = JSON.parse(localStorage.getItem('todos')) || []; | ||
} catch (e) { | ||
todos = []; | ||
} | ||
todos = todos.filter(todo => todo.date === getDate()); | ||
|
||
function saveTodos() { | ||
localStorage.setItem('todos', JSON.stringify(todos)); | ||
} | ||
|
||
// let todos = (JSON.parse(localStorage.getItem('todos') ?? [])).filter(todo => todo.date === getDate()); | ||
// function saveTodos() { | ||
// localStorage.setItem('todos', JSON.stringify(todos)) | ||
// }; <-- JSON 오류가 났던 코드입니다. 왜 이 코드로 하면 오류가 났는지 알아가는 과정에서 새로 배운 것들이 있어 코드 리뷰하시는 분도 보시라고 남겨 놓습니다. | ||
|
||
// todo 정렬 함수 (완료된 일은 밑으로 배치) | ||
function sortTodo() { | ||
todos.sort((a, b) => a.done - b.done); | ||
} | ||
|
||
// todo 렌더링 함수 | ||
function renderTodo() { | ||
todoList.innerHTML = ''; | ||
sortTodo(); | ||
todos.forEach(todo => showTodo(todo)); | ||
getTodoCount(); | ||
} | ||
|
||
// todo 항목 표시 함수 | ||
function showTodo(todo){ | ||
const li = document.createElement('li'); | ||
li.id = todo.id; | ||
|
||
// todo 내용 | ||
const span = document.createElement('span'); | ||
span.innerText = todo.text; | ||
|
||
// 완료 버튼 | ||
todo.done && span.classList.add('done'); | ||
const doneBtn = document.createElement('button'); | ||
|
||
doneBtn.innerText = todo.done === true ? '♥' : '♡'; | ||
doneBtn.onmouseenter = () => doneBtn.innerText = '♥'; | ||
doneBtn.onmouseleave = () => doneBtn.innerText = todo.done ? '♥' : '♡'; | ||
|
||
doneBtn.classList.add('doneBtn'); | ||
doneBtn.addEventListener('click', () => { | ||
span.classList.toggle('done'); | ||
todo.done = !todo.done; | ||
saveTodos(); | ||
renderTodo(); | ||
}) |
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.
전체적으로 함수를 위에서 선언해주시고 그 아래에 필요한 로직을 작성해주셨군요! 이 방식도 안전하고 좋은 것 같아요.
다만, 유선님께서 해주신 것처럼 function 키워드를 활용한 함수 선언식의 경우 함수 정의부가 자동으로 hoisting 되어 아래에 작성되더라도 정상적으로 동작한다고 해요. 반면 화살표 함수(arrow function)
을 이용한 함수 표현식의 경우에는 hoisting이 되지 않고 TDZ라는 개념에 마주하여 원하지 않는 방식으로 동작할 수도 있습니다.
이 부분도 영준님 코드에 리뷰 달아드렸으니 시간날 때 꼭! 확인해보시면 좋겠습니다.
delBtn.classList.add('delBtn'); | ||
delBtn.addEventListener('click', deleteTodo) | ||
|
||
li.appendChild(doneBtn); |
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.
appendChild로 li 요소에 doneBtn, span, delBtn을 추가해 주셨는데 이것을 append(doneBtn, span, delBtn)으로 간소화 하면 어떨까용?!
저도 유선 님과 동일하게 appendChild를 썼었는데 혜인 님의 코드를 리뷰하며 append와 appendChild의 차이점을 명확히 알고 쓸 수 있게 되어 유선 님께도 알려 드립니다👍🏻👍🏻
background-color: #ffffff11; | ||
} | ||
|
||
ul span { |
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.
이 부분이 새롭게 추가된 todo 목록의 text 부분에 대한 css인 것 같은데, 유선 님 todolist를 테스트하다가 영어로 긴 글을 적으면 그 todo 요소 밖으로 삐져 나가더라고요 😂
이 현상은 제 과제에서도 발생했었고 overflow-wrap: break-word; 속성을 사용해 해결한 바 있습니다!
긴 단어가 요소의 너비를 초과할 때, 단어를 중간에서 잘라서 다음 줄로 넘길 수 있게 했습니다
유선 님께서도 이 속성을 적용해 보시면 좋을 듯 싶습니다 🔥🔥🔥
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.
제 과제에서도 이런 오류가 있었는데 저는 word-break: break-all;
속성을 사용해 해결했습니다!
민재 님 리뷰를 보고 overflow-wrap: break-word;
과 word-break: break-all;
의 차이를 찾아봤는데요😶✨
✅overflow-wrap: break-word;
- 단어 단위의 줄바꿈을 우선적으로 고려하여 단어 자체가 컨테이너 너비를 넘을 때 단어 단위로 줄을 바꿈
ex.
안녕하세요. CEOS 프론트
1주차 과제 제출합니다.
✅word-break: break-all;
- 컨테이너 너비를 넘는다면 하나의 단어이더라도 문자 단위로 강제로 줄을 바꿈
ex.
안녕하세요. CEOS 프론
트 1주차 과제 제출합니
다.
사용성을 고려해봤을 때 전자가 조금 더 가독성이 높아서 제가 overflow-wrap: break-word;
속성을 알았다면 이 속성을 선택했을 것 같네요! 이렇게 또 하나 배워갑니다!
@@ -0,0 +1,349 @@ | |||
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ |
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.
저는 이 Normalize.css 라는 스타일시트 라이브러리의 존재를 몰랐었는데... 하나 더 배워갑니다...!!!!!!!! 👍🏻👍🏻
코드 리뷰를 위해 공부한 바로는, Normalize는 브라우저마다 기본 스타일이 조금씩 다른데, css를 통일하지 않으면 특정 브라우저에서는 의도대로 디자인이 되지 않는 이슈가 발생할 수 있어 해결책으로 사용하는 것이군요...... Normalize를 공부하다 보니 Reset이라는 개념도 있어서 더욱 풍부하게 알아갈 수 있었습니다 👍🏻👍🏻
margin-bottom: 20px; | ||
} | ||
|
||
input:focus, input:hover { |
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.
마우스 hover 시 input의 밑줄 색이 바뀌는 게 시각적으로 정말 예쁩니다!!! 👍🏻👍🏻 훨씬 네온사인의 느낌이 나는...
// localStorage.setItem('todos', JSON.stringify(todos)) | ||
// }; <-- JSON 오류가 났던 코드입니다. 왜 이 코드로 하면 오류가 났는지 알아가는 과정에서 새로 배운 것들이 있어 코드 리뷰하시는 분도 보시라고 남겨 놓습니다. | ||
|
||
// todo 정렬 함수 (완료된 일은 밑으로 배치) |
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.
완료된 todo는 밑으로 내려가게 정렬함으로써 아직 완료하지 못한 todo에 집중할 수 있게 하신 것 정말 좋은 생각인 것 같습니다! 저는 결과화면을 구현하는 데 집중하다 보니 이런 디테일적인 부분에서 미흡했는데 또 배워 갑니다 👍🏻
} | ||
|
||
// let todos = (JSON.parse(localStorage.getItem('todos') ?? [])).filter(todo => todo.date === getDate()); | ||
// function saveTodos() { |
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.
정답: 주석으로 남겨주신 코드에서 JSON 오류가 발생하는 이유는 JSON.parse가 유효한 JSON 문자열이 아닐 때 예외를 발생시키기 때문입니다.
특히 localStorage.getItem('todos')가 null일 때 JSON.parse가 실패할 수 있어 JSON.parse의 결과를 처리할 때 항상 예외처리를 하는 게 좋을 것 같습니다.
(맞나용...??? 😣)
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.
css에서 normalize를 사용하신 점이나 전체 코드 구조가 제 코드와 다른 점이 많아서 배워가는 점이 많은 것 같아요! 그리고 네온 느낌의 UI가 특히 너무 멋있습니다!💡👍 과제하느라 수고 많으셨습니다~!🍀
} | ||
|
||
/* 애니메이션 */ | ||
@keyframes fadeInUp { |
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.
CSS로 특별히 큰 동작이 있는 애니메이션을 만든 적이 없어서 transition 정도만 사용해보고 keyframes 속성은 사용해 본 적이 없었는데 이렇게 사용 예시를 보고 사용 방법을 공부하게 되었네요! fadeIn 애니메이션으로 페이지가 훨씬 풍성해보이는 것 같아요!👍
background-color: #ffffff11; | ||
} | ||
|
||
ul span { |
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.
제 과제에서도 이런 오류가 있었는데 저는 word-break: break-all;
속성을 사용해 해결했습니다!
민재 님 리뷰를 보고 overflow-wrap: break-word;
과 word-break: break-all;
의 차이를 찾아봤는데요😶✨
✅overflow-wrap: break-word;
- 단어 단위의 줄바꿈을 우선적으로 고려하여 단어 자체가 컨테이너 너비를 넘을 때 단어 단위로 줄을 바꿈
ex.
안녕하세요. CEOS 프론트
1주차 과제 제출합니다.
✅word-break: break-all;
- 컨테이너 너비를 넘는다면 하나의 단어이더라도 문자 단위로 강제로 줄을 바꿈
ex.
안녕하세요. CEOS 프론
트 1주차 과제 제출합니
다.
사용성을 고려해봤을 때 전자가 조금 더 가독성이 높아서 제가 overflow-wrap: break-word;
속성을 알았다면 이 속성을 선택했을 것 같네요! 이렇게 또 하나 배워갑니다!
// 할 일 삭제 함수 | ||
function deleteTodo(e){ | ||
const li = e.target.parentElement; | ||
li.remove(); | ||
|
||
todos = todos.filter(todo => todo.id !== parseInt(li.id)); | ||
saveTodos(); | ||
renderTodo(); | ||
}; |
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.
삭제 버튼을 클릭했을 때 DOM에서 해당 투두 항목 제거 후 남은 할 일 개수를 업데이트하기 위해 renderTodo()
를 호출하신 것 같아요!
제가 이해한 바로는 renderTodo()
는 전체 목록을 다시 렌더링하는 방식인 것 같은데 아래처럼 getTodoCount()
로 남은 할 일 개수만 업데이트 시켜서 조금 더 효율적으로 렌더링을 진행할 수도 있을 것 같습니다!
// 할 일 삭제 함수 | |
function deleteTodo(e){ | |
const li = e.target.parentElement; | |
li.remove(); | |
todos = todos.filter(todo => todo.id !== parseInt(li.id)); | |
saveTodos(); | |
renderTodo(); | |
}; | |
function deleteTodo(e) { | |
const li = e.target.parentElement; | |
todos = todos.filter(todo => todo.id !== parseInt(li.id)); | |
saveTodos(); | |
li.remove(); | |
getTodoCount(); | |
} |
1주차 미션: Vanilla-Todo
🪄 결과물
🩵 구현 기능
할 일 추가, 완료, 삭제 기능
날짜 표시 및 오늘의 투두리스트 불러오기
완료로 토글 시 해당 항목은 밑으로 배치
'완료된 항목 수 / 전체 항목 수' 표시
로컬 스토리지에 투두리스트 저장
디자인: 반응형, 폰트, 애니메이션, 커스텀 커서, hover 및 focus 시의 스타일 추가
🩵 느낀 점
바닐라 JS를 한 달 정도 배우고 난 뒤로는 사용헤 본 적이 많지 않아서 이번 과제 초반에 애를 좀 먹었던 것 같다. 리액트의 방식에 익숙해져 있다가 JS만으로 개발을 하려니 꽤 불편했다. (리액트의 소중함을 깨닫게 되는 순간!) 또 라이브러리 사용에 제약이 있다 보니 작은 거 하나 추가로 구현하려고 해도 써야되는 코드들이 많아 너무 비효율적으로 느껴졌다. 원하는 기능을 다 넣으려면 정말 코드가 중구난방으로 길어질 것 같았다. 누군가는 코드리뷰로 내 걸 다 읽으셔야 하니... 최대한 필수 기능을 중심적으로, 간결하고 효율적인 코드를 적기 위해 노력했다. (잘 했는지는 모르겠지만...) 그래도 이번 과제를 통해 기본기를 탄탄하게 다질 수 있어서 좋았고, 복습을 열심히 해야겠다는 다짐을 하게 되었다.
💡Key Questions
1️⃣ DOM은 무엇인가요?
Document Object Model의 약자로, 웹사이트를 구성하는 요소이다. DOM은 웹페이지의 모든 구성 요소를 하나의 트리 구조로 표현하는데, 이 트리는 각각의 요소들을 Node(노드)라고 부르고, 이 노드들은 서로 부모-자식 관계를 맺고 있다. 내가 이번에 작성한 index 파일을 예로 들면,
<html>
안에<head>
,<body>
와 같은 큰 줄기가 있고, 그 안에<div>
,<form>
,<ul>
같은 가지들이 있으며 또 그 안에<h2>
,<input>
,<li>
와 같은 더 작은 요소들이 존재한다. 개발자는 프로그래밍 언어를 통해 이 구조에 접근하고 수정할 수 있게 된다.DOM은 웹페이지의 내용이나 구조를 동적으로 수정할 수 있다는 점, 문서의 구조를 명확하게 정의할 수 있다는 점, 접근성 측면에서 유리하다는 점 등의 장점을 가지고 있지만, 단점도 존재한다. 우선 DOM 트리를 처리할 때 성능이 저하될 수 있다는 문제가 있다. 만약 DOM의 트리 구조가 매우 크고 복잡하다면, DOM을 탐색하고 수정하는 과정에서 성능에 큰 부담이 될 수 있다. 또한 모든 브라우저가 DOM을 동일하게 처리하지 않을 수도 있다는 점 또한 고려해야 한다. 간혹 오래된 브라우저에서는 일부 DOM 메서드가 동작하지 않을 수 있다. (이 경우, 폴리필을 사용하거나 트랜스파일러를 사용해서 해결할 수는 있다.)
2️⃣ 이벤트 흐름 제어(캡처링 & 버블링)이 무엇인가요?
클릭, 스크롤, 입력 등과 같은 웹페이지에서 실행되는 사용자의 행동을 이벤트라고 부른다. 이러한 이벤트가 발생했을 때, 해당 이벤트가 웹페이지에서 어떻게 전달되는 지에 관한 것이 바로 이벤트 흐름제어이다. 이벤트 흐름 제어는 이벤트 캡처링과 이벤트 버블링으로 나뉘어지며, 개발자가 이벤트가 발생했을 때 어느 단계에서 이벤트를 처리할 것인지를 결정하는 데 중요한 역할을 한다.
이벤트 캡처링: 위에서 아래로 이벤트가 전달되는 과정이다. 즉, 사용자가 클릭한 요소에 이벤트가 도달하기 전에 그 요소의 부모 요소들부터 먼저 이벤트를 전달받는다. 예를 들어, 내가 어떤
<p>
요소를 클릭했을 때, 그 요소가 트리 구조의 하위에 속해 있어<div>
안에 있고, 그<div>
가<body>
안에 있다면, 결국 이벤트는 트리의 최상위 요소인<html>
에서<body>
,<div>
,<p>
로 내려가며 전달된다. 이 흐름을 캡처링 단계라고 한다.캡처링이 이루어지고 난 뒤에는 이벤트가 해당 요소에서 처리되는 단계를 거친다. 이 때 이벤트는 어딘가로 전달되지 않고 사용자가 실제로 행동한 그 요소에서만 실행된다. 즉, 이벤트가 처리되는 최종 단계라고 할 수 있으며 이 때 해당 요소에 등록된 모든 이벤트 리스너가 실행된다. 만약 캡처링 단계에서 이벤트를 처리해야 할 일이 발생한다면, 이벤트 리스너를 활용할 수 있다.
addEventListener()
메서드를 통해 리스너를 등록하고, 세번째 인자로 true값을 넘겨주면 해당 리스너는 캡처링 단계에서 이벤트를 처리하게 된다.이벤트 버블링: 캡처링과는 반대로 아래에서 위로 이벤트가 전달되는 과정이다. 이벤트가 목표 단계에서 처리된 후, 해당 이벤트가 다시 부모 요소로 전달되는 것이다. 기본적으로 자바스크립트는 버블링 방식을 따르고 있으나, 때로는 이벤트가 부모 요소로 전달되는 것을 막아야 할 때가 있다. 그럴 때는
stopPropagation()
메서드를 통해 중지시킬 수 있다.3️⃣ 클로저와 스코프가 무엇인가요?
클로저: 함수가 선언된 환경을 기억하는 함수를 의미한다. 즉, 클로저는 함수 자신이 만들어졌을 때의 외부 변수를 기억하고, 그 변수를 이후에도 계속 사용할 수 있게 해준다. (A 함수 안에 있는 B 함수는 A함수가 종료된 후에도 A 안에서 선언되었던 변수에 접근할 수 있다는 의미) 클로저는 함수 호출 시마다 동일한 변수를 사용할 수 있어 동작을 효율적으로 관리할 수 있다는 장점이 있지만, 클로저가 여러번 중첩되면 어느 변수가 어디서 온 건지 추적하기 어려워지기 때문에 디버깅이 어려울 수 있다는 단점이 있다.
스코프: 변수가 유효한 범위를 의미한다. 자바스크립트에서는 변수가 선언된 위치에 따라 그 변수를 사용할 수 있는 범위가 결정되는데, 이를 크게 전역 스코프와 지역 스코프로 나눌 수 있다.