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">&times;</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. 방향키로 포커스 이동시 스크롤 이상하게 내려오는 현상
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 // prettier-ignore
? $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();
};
  • 사용자가 입력한 값을 state로 관리하여 그 값과 countryCode의 1번째 요소인 나라이름과 일치하는 부분을 찾아 strong 태그로 감싼 값으로 바꿔주었다.

  • strong 태그 대신 b 태그를 사용하면 안된다.