※이번 포스트로 부터 나온 최종 아웃풋
https://frontend-bear.tistory.com/86
[HTML, CSS, JS] 바닐라 자바스크립트로 MBTI 테스트 만들기(2)에서 작성했던 js를 리팩토링 해보자.
작업 개요
- 코드 가독성 향상 및 반복해서 사용되는 내용 변수화
작업 가이드 및 사담
- 이벤트 리스너 함수들을 분리하여 가독성 향상 / 각 버튼에 대한 클릭 이벤트에 대한 처리를 별도의 함수로 분리
- 클릭한 버튼의 innerText 전달: 클릭한 버튼의 innerText를 이벤트 핸들러 함수로 전달하여 직관성을 향상
- querySelector 결과를 변수에 저장: 반복적으로 querySelector를 호출하는 부분을 변수에 저장하여 가독성을 향상
- 중복 코드 제거: 중복되는 코드를 줄이고 가독성을 개선하기 위해 변수 생성
- UI 요소 변수화: UI 요소들을 변수로 저장하여 가독성을 향상
- 텍스트 콘텐츠와 HTML 내용 설정 분리: 텍스트 콘텐츠와 HTML 내용을 설정하는 부분을 변수로 분리하여 가독성을 향상
- 조건문 단순화: 조건문의 반환 값이 같은 경우를 통합하여 가독성을 향상
전체코드
/* personalTest/js/personalTest.js */
class PersonalTest {
constructor(target) {
this.container = document.querySelector(target); // 추후 dom 내용을 바꾸기 위한 선택자
this.page = 0; // 0: intro, 1: test, 2: result 현재 페이지
this.progress = 0; // 현재 질문 단계
this.questions = {...}; // 질문 모음
this.results = []; // 사용자가 선택한 답모음
this.resultInfors = {...}
this.init();
}
init() {
this.questionArray = this.getQuestion(); // 질문을 배열로 저장
const answerAButton = this.container.querySelector('button[data-answer="a"]');
const answerBButton = this.container.querySelector('button[data-answer="b"]');
const startButton = this.container.querySelector('button[data-action="start"]');
const restartButton = this.container.querySelector('button[data-action="restart"]');
answerAButton.addEventListener('click', () => this.submitAnswer(answerAButton.innerText));
answerBButton.addEventListener('click', () => this.submitAnswer(answerBButton.innerText));
startButton.addEventListener('click', this.start.bind(this));
restartButton.addEventListener('click', this.restart.bind(this));
/*
2023-05-19 리팩토링
1. 이벤트 리스너 함수 분리: 이벤트 리스너를 분리하여 코드 가독성 향상.
2. e.target.innerText 대신 클릭한 버튼의 innerText를 매개변수로 전달. (직관성)
3. querySelector 결과를 변수에 저장: 반복적인 querySelector 호출을 피하여 가독성 향상.
*/
this.render();
}
start() {
if(this.progress !== 0) return; // 진행중이면 실행하지 않음
this.page = 1;
this.render();
}
restart() {
this.page = 0;
this.progress = 0;
this.results = [];
this.render();
}
getQuestion() { // questions의 키를 참조해서 질문을 반환
return Object.entries(this.questions)
.flatMap(([type, questions]) => questions.map(question => ({ ...question, type })));
/*
2023-05-19 리팩토링
1. Object.entries를 사용하여 객체를 배열로 변환 후 이차원 배열을 flatMap으로 평탄화.
*/
}
getCurrentQuestions() { // 현재 progress의 질문을 반환
const currentQuestionIndex = this.progress;
return this.questionArray[currentQuestionIndex];
/*
2023-05-19 리팩토링
1. currentQuestionIndex 변수 도입으로 현재 질문의 인덱스를 명시적으로 표현하여 가독성 향상.
*/
}
submitAnswer(answer) {
const currentQuestion = this.questionArray[this.progress];
if (this.questionArray.length <= this.progress + 1) {
this.page = 2;
this.render();
}
const selectedAnswer = Object.keys(currentQuestion.answer)
.find(selectedAnswer => currentQuestion.answer[selectedAnswer] === answer);
this.results.push({
type: currentQuestion.type,
answer: selectedAnswer
});
this.progress++;
this.render();
return this.getCurrentQuestions();
/*
2023-05-19 리팩토링
1. this.questionArray[this.progress]를 반복해서 사용하는 대신 currentQuestion라는 변수를 도입하여 가독성 향상
2. Object.keys() 및 find() 메서드를 사용하여 사용자가 선택한 답변에 해당하는 키 값을 찾는 과정을 단순화.
*/
}
calcResult() {
const totalResult = Object.keys(this.questions).reduce((acc, cur) => {
acc[cur] = this.results
.filter(result => result.type === cur)
.reduce((acc, cur) => {
acc[cur.answer] = acc[cur.answer] ? acc[cur.answer] + 1 : 1;
return acc;
}, {});
return acc;
}, {});
return this.createPersonalResult(totalResult);
/*
2023-05-19 리팩토링
1. this.result = 부분 제거, totalResult 변수에 할당 이후 중첩 reduce() 메서드를 사용하여 가독성 향상.
*/
}
createPersonalResult(totalResult) {
return Object.keys(totalResult).reduce((acc, cur) => {
const result = totalResult[cur];
if (!result.a) return acc + cur[1];
if (!result.b) return acc + cur[0];
if (result.a === result.b) {
return acc + cur[0];
}
return acc + (result.a > result.b ? cur[0] : cur[1]);
}, "");
/*
2023-05-19 리팩토링
1. totalResult[cur]를 result 변수로 저장하여 가독성 향상
2. if문의 반환 값이 같은 경우를 하나로 통합하여 가독성을 개선
*/
}
render() {
const introContainer = this.container.querySelector('.intro_container');
const testContainer = this.container.querySelector('.test_container');
const resultContainer = this.container.querySelector('.result_container');
if (this.page === 0) {
introContainer.classList.add('active');
testContainer.classList.remove('active');
resultContainer.classList.remove('active');
} else if (this.page === 1) {
testContainer.classList.add('active');
introContainer.classList.remove('active');
resultContainer.classList.remove('active');
const progressElement = this.container.querySelector('.progress');
const questionElement = this.container.querySelector('.question');
const answerAElement = this.container.querySelector('button[data-answer="a"]');
const answerBElement = this.container.querySelector('button[data-answer="b"]');
progressElement.textContent = `Q${this.progress + 1}. `;
questionElement.textContent = this.getCurrentQuestions().question;
answerAElement.textContent = this.getCurrentQuestions().answer.a;
answerBElement.textContent = this.getCurrentQuestions().answer.b;
} else if (this.page === 2) {
resultContainer.classList.add('active');
introContainer.classList.remove('active');
testContainer.classList.remove('active');
const resultTextElement = this.container.querySelector('.result_text');
const resultInforTitleElement = this.container.querySelector('.result_infor_title');
const resultInforElement = this.container.querySelector('.result_infor');
const calcResult = this.calcResult();
resultTextElement.innerHTML = `당신의 성향은 <span class="point_text">${calcResult}</span>입니다.`;
resultInforTitleElement.innerHTML = `[ ${this.resultInfors[calcResult].title} ]`;
resultInforElement.innerHTML = this.resultInfors[calcResult].desc
.split('<br />')
.map(el => `<li>${el}</li>`)
.join('');
}
/*
2023-05-19 리팩토링
1. 각각의 UI 요소를 변수로 저장하여 가독성을 향상
2. 텍스트 콘텐츠와 HTML 내용을 설정하는 부분을 변수로 분리하여 가독성을 개선
*/
}
}