이력서를 넣으면서 프로젝트를 진행하고 싶어 찾아보던중 완전 초기에 팀원들과 페어프로그래밍만으로 만들었던 프로젝트를 열어본 결과 충격에 빠지게 되었고 리팩토링하기를 결정하게 되었다.
혼자서 대부분의 기능을 가지고있던 모듈
같은 부모를 둔 CardGame 컴포넌트에서 Result 페이지로 점수 정보를 넘겨줘야하는 경우가 생겼다. 여러가지 해결책을 생각했었는데 점수와 관련된 부분이라 자바스크립트 객체로 관리하는것이 좋을 것 같다는 생각이 들었다. 점수를 담당하는 객체를 만들고 인스턴스를 내려줌으로써 같은 데이터를 공유하고 메소드를 통해 조작할 수 있도록 하였다.
export default function App({ $target }) {
const scoreManager = new ScoreManager();
this.route = () => {
// Home 페이지 렌더링
if (pathname === "/") {
new Home({ $target: $page, props: { scoreManager } }).setup(); // props로 전달
// CardGame 페이지 렌더링
} else if (pathname.indexOf("/cardGame/") === 0) {
const [, , level] = pathname.split("/");
new CardGame({
$target: $page,
props: { level, scoreManager },
}).setup();
// Result 페이지 렌더링
} else if (pathname === "/result") {
new Result({ $target: $page, props: { scoreManager } }).setup(); // 동일 객체를 props로 전달
}
};
// 카드 관련 관심사를 처리하는 CardManager 생성자 함수
export default function CardManager() {
this.setCardList = async (initCount) => {
...중략
};
this.cardFlip = ($target) => {
...중략
};
this.initCard = ($target) => {
...중략
};
this.getCardPosition = ($target) => {
...중략
};
...중략
}
export default function TimerManager(scoreManager) {
this.scoreManager = scoreManager;
this.timerId;
this.clearTime;
this.setTimer = (setState, limitTime) => {
this.timerId = setInterval(() => {
if (!scoreManager.getScoreData().winOrLose) {
this.clearTime = --limitTime;
setState({ value: limitTime });
if (this.isClear()) this.endGame("defeat");
} else {
scoreManager.setClearTime(this.clearTime);
this.endGame("win");
}
}, 1000);
};
...중략
}
게임을 재시작 할때 ProgressBar, CardList 객체가 새로 생성되고 타이머가 계속 반복되는 현상을 발견했다.
원인은 재시도 버튼을 누르면 페이지를 생성하는 함수를 다시 실행하게 되고 setup 함수가 반복되면서 이벤트 리스너를 중복해서 설치하게 되어 메모리 누수와 함께 타이머도 재시도 숫자만큼 생성하게 된것이었다.
root 요소에 모든 이벤트 리스너를 추가해서 이벤트 위임으로 동작하게 했는데 처음에는 언마운트 될때 이벤트 리스너를 지우고 싶었지만 다수의 이벤트 리스너를 제거하는 방법은 생각보다 까다로웠다.
여러 방법을 생각하던 중 DOM 요소를 지우면 참조되지않는 이벤트 리스너는 가비지 컬렉션 당할것으로 생각했고 root 아래 <div class="page"></div>
요소를 생성해서 이벤트 리스너를 부착하고 페이지 이동 시 교체하는 방식으로 변경하여 해결되었다.
this.route = () => {
const { pathname } = location;
$target.innerHTML = "";
// 주소가 변경될때마다 페이지 요소를 만들어 $target(root)에 갈아 끼우게된다.
const $page = document.createElement("div");
$page.className = "page";
$target.appendChild($page);
if (pathname === "/") {
new Home({ $target: $page, props: { scoreManager } }).setup();
}
...중략
}
이전 React 프로젝트 같은 경우 netlify 통해서 배포하고 다른경로에서 새로고침이 생기면 _redirects
파일을 추가해 모든 경로에서 index.html을 다시 주는 방법을 사용했지만 해당 프로젝트에서는 새로고침이 일어나고 index.html에서 리소스 요청 시 모든 파일에 html파일이 응답으로 오는것을 확인했다.
그로인해서 index.js는 "text/html" 응답 받은것으로 Error를 뿜었다.
간단하게 생각하고 만든 프로젝트라서 정적페이지로 index.html 만 배포하려고 했었는데 배포 후에 발생하는 문제들을 생각하니 express.js로 간단한 서버를 만들어서 문제를 해결하기로 마음먹었다.
const express = require("express");
const path = require("path");
const app = express();
const port = process.env.PORT || 3000;
app.use("/static", express.static(path.resolve(__dirname, "static")));
app.get("/*", (req, res) => {
res.sendFile(path.resolve(__dirname, "index.html"));
});
app.listen(port, () => {
console.log(`Server running ....`);
});
모든 경로에 index.html을 반환하되 리소스 파일들이 있는 /static
경로로 요청이 오면 파일들을 반환하게 함으로 해결하였다.
간단하게 쉬어가는 생각으로 리팩토링을 진행했지만 문제도 많았고 배운것도 많은 리팩토링이 되었다.
2023-08-16
netlify
를 사용해서 배포할때redirect
경로의 우선순위를 정할 수 있었습니다.express.js
를 사용하는것보다는 배포한 서비스의 기능을 사용하는것이 편리할 것 같습니다.
이 게임 재밌더라구요?🤭
호수님 글 잘 봤습니다! 술술 읽히도록 잘 쓰시네요😁!