1주차 과제는 3가지 요구사항이 있었다.
app.js에 주어진 Clock 컴포넌트를 재사용하여 app.js 하단에 Digital Clock을 추가하기.1️⃣ Digital Clock은 현재 시, 분, 초를 표기해주어야 함.
2️⃣ Digital Clock의 시간은 매초마다 새롭게 갱신되어야 함.
const Clock = CanolaUI.create({
events: {
".clock h3": function onTitleClick() {
// ".clock h3" 선택자에 해당하는 요소를 클릭했을 경우, 실행됩니다.
console.log("Click H3!");
},
".clock p": function onTimeClick() {
// ".clock p" 선택자에 해당하는 요소를 클릭했을 경우, 실행됩니다.
console.log("Click P!");
},
},
template: function () {
return `
<div class="clock" style="background-color: ${this.backgroundColor};">
<h3>${this.title}</h3>
<p>${this.time}</p>
</div>
`;
},
});
3가지 요구 사항을 모두 충족시키긴 하였으나 code review를 통해 개선해야 할 점을 알려주셨다.
1️⃣ CanolaUI.create는 본래 Clock만을 위한 것이 아닌 다른 컴포넌트가 될 수도 있다.
Canola/Component.js 에서 내가 구현한 addEvents는 click 이벤트"만"을 위해서 구현되어있다.
Component.prototype.addEvents = function() {
for (let key in addEvents) {
const clockElement = document.querySelector(key);
clockElement.addEventListener("click", addEvents[key]);
}
};
클릭 이벤트 말고도 다른 이벤트를 넣고 싶다면 어떻게 할 수 있을까? 지금 상태라면 Component에 또 다른 prototype 메소드를 추가해야할 것 같다. 하지만 각각의 특징을 가진 컴포넌트가 불필요한/관련 없는 메소드를 가지는 것은 다소 비효율적으로 보임.
app.js 31번 줄의 내용을 다시 한번 확인해보자. 과제가 시계를 구현하는 것이기 때문에 혼란이 올 수 있지만 CanolaUI.create는 본래 Clock만을 위한 것이 아닌 다른 컴포넌트가 될 수도 있다.
events 정보는 Component가 생성되는 인스턴스가 참조할 수 있도록 전달만 해주고 상세한 구현은 CanolaUI.create로 생성된 Clock의 prototype을 통해 구현하는 것이 좀 더 타당해보인다. 왜냐하면 Clock 컴포넌트만을 위한 기능이 될 수 있기 때문..
아래의 예시를 참조해보자.
// Component.js
function componentFactory(generateTemplate, events) {
...
// CanolaUI.create 로 생성된 특정 컴포넌트가 events 값을 참조할 수 있도록 설정.
this.events = events;
return Component;
}
// app.js
Clock.prototype.addClickEvent = function () {
const { $el, events } = this;
// ... Your logic for the click event ...
};
+추가 내용 1) Component.prototype.addEvents가 해당 위치에 있다는 것이 잘못되었다는 의미는 아니지만 click 이벤트를 다양한 형태의 컴포넌트에서 공통으로 사용하게 한다면 해당 위치가 적합할 수 있다. 그렇게 된다면 addClickEvents로 명칭을 하는 것이 더욱 명확할 것 같다.
+추가 내용 2) 컴포넌트에서 다양한 이벤트 등록을 여러 컴포넌트에서 하고싶다고 가정한다면 기존의 addEventlistener와 같은 방식으로도 설계 해볼 수 있을 것 같다. (하나의 아이디어일 뿐이니 참고만)
// Component.js
function componentFactory(generateTemplate, events) {
// ...
Component.prototype.addEventListener = function (type, callback) {
if (!type) throw new CanolaError('Please input event type');
if (callback && typeof callback !== 'function')
throw new CanolaError('Callback is not function');
for (let key in events) {
const target = this.$el.querySelector(key);
if (!target) throw new CanolaError(`'${key}' : 존재하지 않는 엘레먼트입니다.`);
target.addEventListener(type, callback || events[key]);
}
return this;
};
Component.prototype.removeEventListener = function (callback) {
if (callback && typeof callback !== 'function')
throw new CanolaError('Callback is not function');
for (let key in events) {
const target = this.$el.querySelector(key);
if (!target) throw new CanolaError(`'${key}' : 존재하지 않는 엘레먼트입니다.`);
target.removeEventListener(callback || events[key]);
}
return this;
};
return Component;
}
2️⃣ options.events가 없을 경우 에러 처리를 해줄 수도 있을 것 같다.
return componentFactory(options.template);
➡️ return componentFactory(options.template, options.events);
3️⃣
const digitalClock = new Clock({
root: "#root",
title: "현재 실행 시간",
backgroundColor: "#DEEAFF",
time: initialTime,
});
digitalClock.render();
digitalClock.addEvents();
const $currentTime = document.querySelector(".clock p");
setInterval(() => {
$currentTime.textContent = getTimeStamp();
}, 1000);
현재는 DOM 내에 .clock p에 해당하는 요소를 잡아서 업데이트 하는 방식이다.
만약 digitalClock 컴포넌트가 여러개 만들어져있고 그중에서 몇개는 시간을 멈추거나, 초 간격을 바꾸고 싶다면 어떻게 해야할까?
현재의 구현 방식이라면 쉽지 않을 것 같다. 그리고 설령 기능을 붙이더라도 정상적으로 작동하기 어려울 것 같다. (왜냐하면 .clock p을 잡고 있는 $currentTime 변수가 의도한 clock 맞는지 확신할 수 없기 때문이다.)
컴포넌트는 말그대로 하나의 블록이며, 컴포넌트는 자신만의 고유의 상태를 가질 수 있어야 한다. 그럼 왜 컴포넌트는 고유의 상태를 가져야할까?
외부의 데이터 의존성을 최소화하여 의도치 않은 변경이나 버그 발생을 줄이고, 과제에 있던 코멘트처럼 하나의 목적을 위한 최소한의 기능을 컴포넌트 단위로 나누어 재사용성 증대와 유지보수를 용이하게 하기 위해서 이다.
new keyword를 통해 생성된 컴포넌트는 스스로를 this로 접근할 수 있습니다. 그렇다면 this가 각자 타이머 함수를 가지고 있다면 어떨까? 생성자의 prototype을 이용하면 외부에 영향 없이 컴포넌트마다 독립적으로 자신만의 기능을 사용할 수 있을 것 같다. 아래 예시를 참조해보자.
Clock.prototype.start = function () {
// Component.js 내의 componentFactory 통해 반환되는 this에 $el 프로퍼티로 노드에 접근할 수 있다. 해당 함수의 내용과 render 함수도 한번 다시 보시면 좋을 것 같다.
const { $el } = this;
// ... LOGIC ...
};
1️⃣ 메서드와 this
안녕하세요 진권님. 4번째 과제 진행하시느라 수고하셨습니다.👍 Advanced까지 구현해주시고 고민하신 흔적이 많이보이네요.
질문과 관련해서 innerHTML의 취약성을 방지하기 위해서 DOM 접근 + textContent으로 접근하신 건 좋은 방법이라 생각합니다. 하지만, 앞서 코멘트 드린 것과 같이 두번째 방식에서 this와 prototype을 사용해 함수의 재사용성을 끌어올리면 더욱 좋을 것 같습니다.
this는 함수의 유연성과 재사용성을 뛰어나게 만들어주기 때문에 정말 중요한 부분이고, 특히 객체 지향 프로그래밍에서 많이 활용됩니다.
this 대한 이해를 꼼꼼하게 해주시고, 제가 남겨드린 코멘트에 대한 업데이트도 함께 해보시면 더욱 좋을 것 같습니다.
객체 지향 프로그래밍 관련 링크도 붙여두었으니 확인해보시고 앞으로도 파이팅입니다. 고생하셨습니다.👏👏
Object-Oriented Programming | PoiemaWeb