이제까지 바닐라 자바스크립트로 과제를 진행할 때 항상 렌더링에 큰 불편함을 느꼈습니다.
저번주에 작성한 코드의 일부입니다.
바닐라 자바스크립트로 SPA등 을 구현할 때 innerHTML
을 사용합니다.
이 방법을 사용하면 이런 문제들을 만날 수 있습니다.
export default class MenuBar {
constructor($target) {
this.$target = $target;
this.render();
}
init() {
this.$target.addEventListener("click", (event) => {
...
});
}
render() {
// Layout을 먼저 잡고 component를 그려내는 코드
this.$menuBar = document.createElement("div");
this.$menuBar.className = "menuBar";
this.$menuBar.innerHTML = `
<div class="section1">
<button class="closeButton">X</button>
</div>
<div class="section2" />`;
this.$target.appendChild(this.$menuBar);
this.history = new History(document.querySelector(".menuBar .section2"));
this.init();
}
}
재귀적으로 DOM 계층을 만드는 함수를 작성해서 이 문제를 해결했습니다.
import { div, button, span } from "@core/CreateDom";
div() // documentCreateElement("div")
span() // documentCreateElement("div")
button() // documentCreateElement("div")
// 선언적으로 자식을 추가할 수 있음
div(
div(),
div()
)
// 커링을 이용해서 property와 event를 지정할 수 있음
div({event:{class:"wrapper",click:()=>{console.log()}}})(
div(),
button()
)
// 실제 프로젝트에 썼던 코드들
return div({ class: "inputBox payment" })(
div({ class: "inputItem paymentMethod", event: { click: toggleIsClick } })(
div({ class: "text_bold_small label", role: "label" })("결제수단"),
div({ class: `text_body_regular dropdown` })(
span({ class: `dropdownInput ${paymentMethod ? "active" : ""}` })(
`${paymentMethod ?? "선택하세요"}`,
),
span({ class: "smallIcon" })(downArrowIcon()),
),
state.isPaymentMethodClick && PaymentDropdownPanel({ state, ref }),
),
);
get
/ set
함수가 호출될 때 원하는 작업을 하도록 진행export const { makeObservable, subscribe } = (function () {
const observerMap = new Map();
function makeObservable(state) {
return new Proxy(state, {
get(_, property) {
// ...
return Reflect.get(...arguments);
},
set(target, property, value, receiver) {
const handlerSet = observerMap.get(receiver);
const isChange = target[property] !== value;
if (!(handlerSet && isChange)) {
return true;
}
Reflect.set(...arguments);
handlerSet.forEach((handler) => {
const isSuccess = handler();
if (!isSuccess) {
handlerSet.delete(handler);
}
});
return true;
},
});
}
function subscribe(state, handler) {
const handlerSet = observerMap.get(state);
if (handlerSet) {
handlerSet.add(handler);
} else {
observerMap.set(state, new Set([handler]));
}
}
return { makeObservable, subscribe };
})();