darkMode 리팩터링 중 느낀점

1. setDarkMode 함수의 응집도를 높이고 관심사를 구분하자.

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
const setDarkMode = (() => {
const $body = document.body;

const userTheme = window.matchMedia("(prefers-color-scheme: dark)").matches;
const transitionDuration =
+getComputedStyle(document.documentElement).getPropertyValue(
"--transition-duration"
) * 1000;

const getCurrentMode = () => JSON.parse(localStorage.getItem("isDark"));

const toggleMode = () => {
$body.classList.toggle("dark", getCurrentMode());
};

const setCurrentMode = (selectedMode) => {
localStorage.setItem("isDark", selectedMode);
toggleMode();
};
return {
toggle() {
setCurrentMode(!getCurrentMode());
},
init() {
localStorage.getItem("isDark") === null
? setCurrentMode(userTheme)
: toggleMode();
},
preventBlink() {
setTimeout(() => {
$body.classList.remove("hide");
}, transitionDuration);
},
};
})();

document
.querySelector(".toggle-button")
.addEventListener("click", setDarkMode.toggle);

window.addEventListener("DOMContentLoaded", setDarkMode.init);

window.addEventListener("load", setDarkMode.preventBlink);
  • setDarkMode 함수와 관련된 변수와 함수를 한곳에 모아 응집도를 높이고 즉시실행함수로 감싸고 클로저를 구현하였다.

  • 즉시실행함수의 반환값인 toggle, init, preventBlink 함수는 자유변수를 참조하고 외부함수보다 생명주기가 긴 클로저이다.

  • preventBlink 함수와 init 함수는 결국 렌더링과 관련된 함수여서 하나로 합칠까 생각했지만, 그 경우, 이미지 파일이 많은 프로그램인 경우 이미지가 로드되기 전에 darkMode를 동작이 제대로 되지 않을 수 있다고 판단하여 다른 함수로 구분하였다.

  • addEventListener는 가능한 한줄로 쓰자는 팀원과의 컨벤션을 지키기 위해 위와같이 작성하였고 이벤트 핸들러가 많아지게 되면서 복잡해지더라도 한줄로 있으면 가독성을 높일 수 있을 것이라 생각했다.

popupModal 리팩터링 중 느낀점

1. 변수를 사용한 이유

1
2
3
const $modalContainer = document.querySelector(".modal-container");
const $modal = document.querySelector(".modal");
const $popupMessage = document.querySelector(".popup-message");
  • 위의 document.querySelector() DOM API는 처음에 한번만 요소를 가져와 변수에 할당해주면 된다. 굳이 이벤트 핸들러 함수 내부에 적어서 함수가 호출될 때마다 DOM API까지 같이 호출될 필요가 없으므로 변수에 값을 저장해서 사용하였다.

2. 누군가가 코드를 읽을 때, 한번 더 생각하게 하지 않도록 작성하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 1번
toggle(method) {
$modal.classList[method]('is-open');
$modalContainer.classList[method]('background-gray');
},

// 2번
toggle() {
$modal.classList.toggle('is-open');
$modalContainer.classList.toggle('background-gray');
},
remove() {
$modal.classList.remove('is-open');
$modalContainer.classList.remove('background-gray');
},
  • 처음에 중복을 줄이기 위해 1번으로 구현했었는데, 오히려 직접 사용해보니 2번의 경우가 가독성이 더 낫다고 판단했다. 그 이유는 이벤트 핸들러에서 toggle(‘remove’) 이런식으로 사용하는 것보다 toggle, remove를 사용하는 것이 더 깔끔하기 때문이다.

3. 이벤트 핸들러 축약표현 알아두기

1
2
3
4
5
6
7
render(e) {
e.preventDefault();
$popupMessage.textContent = `from popup : ${e.target.firstElementChild.value}`;
popupModal.toggle('remove');
}

document.querySelector('.popup-form').addEventListener('submit', render);
  • render 함수를 이벤트 핸들러로 등록할 때, event 객체를 받아서 e.preventDefault() 메서드를 실행하는데, 이벤트 핸들러에서는 인수를 전달해주지 않아도 축약표현으로 인식하여 event 객체를 해당 위치에 전달해준다.

stopWatch 리팩터링 중 느낀점

1. 배열 디스트럭처링 사용

1
const [$leftButton, $rightButton] = [...document.querySelectorAll(".control")];
  • leftButton, rightButton / laps, title은 동일한 클래스를 가져 querySelectAll로 불러와서 인덱스 값으로 접근하는 것이 아닌 배열 디스트럭처링 할당으로 식별자를 할당해주었습니다.

2. 버튼 요소의 textContent 값으로 조건문을 구분하는 것은 좋지 않다.

1
2
3
4
5
6
7
8
9
10
11
12
// 1번
if ($leftButton.textContent === "Stop") {
...
}

// 2번
let state = {
isStarted:true
}
if (state.isStarted) {
...
}
  • 1번 처럼 textContent로 조건문을 사용하게되면 나중에 textContent를 수정하게되면 유지보수가 어려워지므로 상태로서 관리하는 것을 추천한다.

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
26
27
28
29
30
31
32
33
34
35
const render = () => {
if (state.isStarted) {
$leftButton.textContent = "Stop";
$rightButton.textContent = "Lap";
$rightButton.removeAttribute("disabled");

$laps.innerHTML = `Laps
${state.laps.map((_, index) => `<div>${index + 1}</div>`).join("")}
`;
$time.innerHTML = `Time
${state.laps.map((lap) => `<div>${lap}</div>`).join("")}
`;
} else {
$leftButton.textContent = "Start";
$rightButton.textContent = "Reset";
}
};

const setState = (newState) => {
state = { ...state, ...newState };

render();
};

const toggleStarted = () => {
setState({ isStarted: !state.isStarted });

updateTime();
};

const addLap = () => {
const newLap = $display.textContent;

setState({ laps: [...state.laps, newLap] });
};
  • render 함수는 leftButton, rightButton을 클릭했을 때, 화면에 글자가 바꾸는 일을 해준다.

  • setState 함수는 데이터를 변경하고 그 데이터를 가지고 화면에 render 하는 함수를 호출하는 역할을 한다.

  • toggleStarted 함수는 isStarted 데이터를 변경시키고 시간을 갱신하는 역할을 한다.

  • addLap 함수는 $display의 textContent값으로 laps 데이터를 변경시키는 역할을 한다.

스톱워치에 아직 리팩터링 중인데, 버튼은 2개인데 가정해야할 상황이 4가지여서 어떻게 짜면 좋을지 고민중이다…