랜더링 문제 해결

이제까지 바닐라 자바스크립트로 과제를 진행할 때 항상 렌더링에 큰 불편함을 느꼈습니다.

저번주에 작성한 코드의 일부입니다.

바닐라 자바스크립트로 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 }),
        ),
    );

Observer 패턴

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 };
})();