stopWatch 회고

1. 복잡한 삼항 연산자를 메서드로 간단하게

1
2
3
4
5
6
const formatElapsedTime = (() => {
// 1 => '01', 10 => '10'
const format = (n) => (n + "").padStart(2, 0);
// const format = n => (n < 10 ? '0' + n : n + '');
return ({ mm, ss, ms }) => `${format(mm)}:${format(ss)}:${format(ms)}`;
})();
  • 삼항 연산자로 조건에 따라 문자열을 직접 넣어서 구현하였는데 padStart 메서드를 사용하면 가독성을 키웠다.
  • 리팩터링 전에는 minutes, seconds 같은 변수를 사용했는데, format 함수를 사용하여 중복을 제거해주었다.

2. 일관성있는 코드를 작성하자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const renderLaps = (() => {
const $laps = document.querySelector('.stopwatch > .laps');

// 랩 타임을 생성하고 DOM에 반영한다.
const createLapElement = (newLap, index) => {
const $fragment = document.createDocumentFragment();

const $index = document.createElement('div');
$index.textContent = index;
$fragment.appendChild($index);

const $newLab = document.createElement('div');
$newLab.textContent = formatElapsedTime(newLap);
$fragment.appendChild($newLab);

$laps.appendChild($fragment);

$laps.style.display = 'grid';
};
  • 리팩터링 전에는 addLap 함수에서 insertAdjacentHTML 메서드 사용하고 reset 함수에서는 innerHTML을 사용하여 일관성이 떨어졌다.

  • 위 코드는 fragment 라는 서브 DOM을 구성하여 기존 DOM을 추가하는 용도로 사용하여 일관성을 갖췄다.

3. 템플릿 건드리지 말라면 건드리지 말기

1
2
3
4
5
6
7
8
9
10
11
12
13
// 리팩터링 전
$laps.innerHTML = `
<div class="lap-title">Laps</div>
<div class="lap-title">Time</div>
`;

// 리팩터링 후
const removeAllLapElement = () => {
document
.querySelectorAll(".laps > div:not(.lap-title)")
.forEach(($lap) => $lap.remove());
$laps.style.display = "none";
};
  • 요구사항에 맞게 템플릿을 건드리지 않아야한다면 템플릿을 건드리지 않아야 하므로 innerHTML로 새로운 값을 할당하는 대신, 자식 노드에 반복문을 사용하였다.

Tabs 회고

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 리팩터링 전
window.addEventListener("DOMContentLoaded", init);

$tabs.addEventListener("click", (e) => {
const $navItems = [...document.querySelectorAll(".tab-content")];
if (!e.target.matches(".tab")) return;

activateTab(e, $navItems);
});

// 리팩터링 후
document.querySelector("nav").addEventListener("click", (e) => {
if (!e.target.classList.contains("tab")) return;

currentTabIndex = +e.target.dataset.index;

document.querySelector(".glider").style.transform = `translate3D(${
currentTabIndex * 100
}%, 0, 0)`;
document.querySelectorAll(".tab-content").forEach(($tabContent, i) => {
$tabContent.classList.toggle("active", i === currentTabIndex);
});
});
  • async init 함수 내부에서 $tabs 이벤트 핸들러 등록까지 함께 해줘서 응집도 높은 코드를 설계할 수 있다.

autocomplete 회고

1. 정규표현식에 콜백함수 사용

1
2
3
4
5
6
7
8
// 리팩터링 전
country.replace(regExp, `<strong>${country.match(regExp)}</strong>`);

// 리팩터링 후
name.replace(
new RegExp(`(${searchString})`, "i"),
(matched) => `<strong>${matched}</strong>`
);

2. 요구사항을 잘 읽고 적합하게 설계하자.

1
2
3
4
5
6
7
8
9
10
const selectSuggestItem = ($selectedItem) => {
// prettier-ignore
$toggleButton.innerHTML = ($selectedItem
? $selectedItem.innerHTML
: `<span>
<i class="bx bx-search"></i>
<span class="autocomplete-title">Select a country</span>
</span>`
) + `<i class="bx bx-caret-down"></i>`;
};
  • 리팩터링 이전 코드는 li 요소 안의 span 요소를 클릭하면 제대로 동작하지 않았다. 그래서 위와 같이 로직을 수정을 해줬어야 했다. 그러므로 꼭 요구사항을 적절히 읽고 그에 적절한 코드를 작성하는 습관을 기르자.

Carousel 회고

1. transitionend 이벤트가 발생했다 안하는 현상

1
2
3
4
5
6
$container.addEventListener("transitionend", carouselSlider.completeTransition);

$container.addEventListener(
"transitioncancel",
carouselSlider.completeTransition
);
  • transition 이벤트가 완료 되었을 때, 이벤트 핸들러를 호출하도록 코드를 작성하였다.
    그런데 transitionend 이벤트가 발생하기 전 즉, transition이 완료되기 전에 transition-property가 제거되거나 display:none 으로 설정이 변경되면 이벤트가 생성되지 않는다.

그래서 보통은 안전하게 CSS 속성 변화가 되었을 때, transitionend 이벤트를 사용하기 위해 라이브러리를 사용하는데, 요구사항에서 라이브러리를 사용하지 말라고 하였으므로, transitioncancel 이벤트를 사용하였다.

transitionend이벤트와 transitioncancel이벤트는 양방향으로 발생한다. 즉, transitionend 이벤트가 발생하면 transitioncancel 이벤트는 발생하지 않는다.