modal 창 구현 중 느낀점
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <body> <div class="modal-container"> <div class="modal"> <h2>Hello</h2> <p>희준이주화이팅</p> <form action="/" class="popup-form"> <input type="text" class="popup-input" /> <button class="popup-ok">OK</button> </form> <button class="popup-cancel is-close">Cancel</button> <button class="popup-close"> <i class="bx bx-x is-close"></i> </button> </div> </div> <script> const $modal = document.querySelector(".modal"); const $modalContainer = document.querySelector(".modal-container");
document.querySelector(".popup-button").addEventListener("click", () => { $modal.classList.toggle("is-open"); $modalContainer.classList.toggle("background-gray"); }); </script> </body>
|
- body 태그 안에 modal div 박스 하나만 만드는 것이 아닌 다음과 같이 modal container로 감싸주는 것이 편하다.
그렇지 않으면 modal 배경색을 입히기가 까다로워진다.
stopwatch 구현 중 느낀점
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| let timeId; const updateTime = () => { const startTime = Date.now();
timeId = setInterval(() => { const date = new Date(Date.now() - startTime); const milliseconds = Math.floor(date.getMilliseconds() / 10); const seconds = date.getSeconds(); const minutes = date.getMinutes();
$display.textContent = `${minutes < 10 ? `0${minutes}` : `${minutes}`}:${ seconds < 10 ? `0${seconds}` : `${seconds}` }:${milliseconds < 10 ? `0${milliseconds}` : `${milliseconds}`}`; }, 10); };
|
- 시간을 수정하는 로직을 함수로 구분하여 역할을 나눠보려 하였다.
Model, Controller, View 생각하면서 리팩토링 해보자.
Tab 구현 중 느낀점
1. 비동기 코드로 전달받은 데이터 다루기
1 2 3 4 5 6
| const state = { datas: [], }; fetchTabsData().then((tabDatas) => { state.datas = tabDatas; });
|
- fetchTabsData() 함수가 프로미스르 반환하는데, 프로미스 콜백함수가 1초 뒤에 데이터를 resolve 인자로 전달해준다. 그러므로 비동기로 받아온 데이터를 state로 관리해주도록 하였다.
2. CSS 사용자 정의 변수 변경 또는 값 취득하기
1 2 3
| const $tabs = document.querySelector(".tabs");
$tabs.style.setProperty("--tabs-length", 3);
|
- Js를 사용하여 CSS에서 설정한 사용자 정의 변수를 가져올 수도 있고 위처럼 직접 값을 설정해줄 수도 있다.
3. 비동기로 데이터를 가져오니 화면에서 데이터를 렌더링 해주기 전에 대기시간을 주자
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| setTimeout(() => { const { datas } = state;
$tabs.style.setProperty("--tabs-length", datas.length); document.querySelector(".spinner").style.display = "none";
$tabs.innerHTML = ` <nav> ${datas .map( (data, index) => `<div class="tab" data-index="${index}">${data.title}</div>` ) .join("")} <span class="glider"></span> </nav> ${datas .map( (data, index) => `<div class="tab-content ${index === 0 ? "active" : ""}"> ${data.content} </div>` ) .join("")} `; }, 1000);
|
- 위 코드는 리팩토링을 하면서 조금더 역할을 나눠서 수정해봐야겠다.
4. template literal로 연산을 하려면 다음과 같이 해야한다.
1 2 3 4 5 6 7 8 9 10
| $tabs.addEventListener("click", (e) => { if (!e.target.matches(".tab")) return;
document.querySelector(".glider").style.transform = "translateX(" + e.target.dataset.index * 200 + "px)";
document.querySelector(".glider").style.transform = `translateX(${ e.target.dataset.index * 200 }px)`; });
|
- 처음에 연산결과를 ${}안에 넣지 않고
document.querySelector('.glider').style.transform = ${e.target.dataset.index} 200 px
이런식으로 했는데 이렇게 하면 안되고 위처럼 해야한다.
Toaster 구현 중 느낀점
1. toastBox 재사용을 위해 함수로 만들었다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const newToastBox = (type, title, Message) => { const $div = document.createElement("div");
$div.classList.add("toast", type); $div.innerHTML = ` <h4 class="toast-title">${title} ${ [...document.querySelectorAll("body .toast")].length }</h4> <div class="toast-message"> <svg width="24" height="24"> <use xlink:href="#${type}" /> </svg> <p>${Message}</p> </div> <a class="toast-close">×</a>`;
$div.style.bottom = "0"; return $div; };
|
- ToastBox 를 재사용하기 위해 함수를 사용하는 것을 적절했으나, 함수가 너무 많은 역할을 하고 있다. 렌더링부터 div 생성과 반환을 함께 하고 있어 리팩토링이 필요하다.
2. render 함수는 화면에 rendering 해주는 역할을 한다.
1 2 3 4 5 6 7
| const render = () => { [...document.querySelectorAll("body .toast")].forEach((toast, index) => { toast.style.bottom = ([...document.querySelectorAll("body .toast")].length - index - 1) * 100 + "px"; }); };
|
- 위 render 함수는 토스트 요소에 bottom style 값을 할당해주는 역할을 하고 있으므로, 리팩토링이 필요하다.
Autocomplete 구현 중 느낀점
1. e.target.matches() vs e.target.closest()
1 2 3 4 5
| document.querySelector("body").addEventListener("click", (e) => { if (!e.target.closest(".autocomplete")) { document.querySelector(".autocomplete-suggester").classList.add("hide"); } });
|
해당 요소 바깥쪽을 클릭하였을 때 .autocomplete-suggester에 hide 클래스를 추가해주는 로직이다.
e.target.closest(‘.autocomplete’)는 이벤트 발생한 요소의 상위 요소를 찾아가면서 인자로 넘겨준 요소를 찾고 반환한다.
만약 없다면 null 값을 반환한다.
하지만, e.target.matches(‘.autocomplete’)는 이벤트 발생한 요소가 인자로 넘겨준 요소를 가지고 있는지 판단하여 불리언값을 반환한다.
matches()의 경우 이벤트 전파를 캐치하기가 어려우므로 상황에 따라 적절하게 사용하자.
- 방향키로 포커스 이동시 스크롤 이상하게 내려오는 현상
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| $suggestList.addEventListener("keydown", (e) => { if (e.key === "ArrowDown") { e.preventDefault(); e.target.nextElementSibling === null ? $suggestList.firstElementChild.focus() : e.target.nextElementSibling.focus(); } if (e.key === "ArrowUp") { e.preventDefault(); e.target.previousElementSibling === null ? $suggestList.lastElementChild.focus() : e.target.previousElementSibling.focus(); } });
|
방향키로 포커스 이동시 한번 이동할 때 마다 스크롤도 같이 내려와서 어색하게 동작하는 것을 방지하지 위해 방향키 값이 “ArrowDown”, “ArrowUp” 일 때만 e.preventDefault();
를 해주었다.
focus() 메서드를 사용하여 선택 요소에 포커스를 선택해주었다.
3. lodash로 debounce 구현 제대로 하기
1 2 3 4 5 6
| $searchInput.addEventListener( "keyup", _.debounce(() => { setState({ inputValue: $searchInput.value }); }, 500) );
|
위와 같이 “keyup” 이벤트 발생 시 lodash debounce 메서드를 이벤트 핸들러로 등록해줘야한다.
또한, debounce의 콜백함수에 실행할 코드를 입력해줄 때, setState함수가 인수를 받는다면 위와같이 써줘야한다.
단, 인수를 받지 않는다면 _.debounce(setState,500) 이런식으로 사용할 수 있다.
4. 사용자가 입력한 값과 countryCode를 state로 관리하였다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| let state = { inputValue: "", countryCode: [], };
const setState = (newState) => { state = { ...state, ...newState }; const regExp = new RegExp(`${state.inputValue}`, "i"); state.countryCode = countryCode.filter((country) => country[1].match(regExp)); state.countryCode = state.countryCode.map((country) => [ country[0], country[1].replace(regExp, `<strong>${country[1].match(regExp)}</strong>`), ]); render(); };
|