Tagged template literals component

신석진( Seokjin Shin)·2023년 2월 12일
0

개요

Tagged template literals를 사용하여 component처럼 사용해보고 싶었다.(styled-component에서 styled.div``으로 선언하는 것에 영감을 받았다.)

기본적으로 static 메소드로 구현하여 다른 컴포넌트에서 특정 메소드에 접근할 수 있도록 하였다.

source: https://github.com/ssj9685/tagged-template-literals-component/
page: https://ssj9685.github.io/tagged-template-literals-component/

예시

요소

Template literal에 들어가는 변수 부분에는 컴포넌트가 들어온다고 가정하고 만들었다.
텍스트와 컴포넌트가 알맞은 위치에 들어가게 하기 위해서 append라는 함수를 선언하여 넣어주었다.

export class Elements {
  static append(text, vars, element) {
    for (const [i, t] of text.entries()) {
      element.append(t);
      element.append(vars[i] ?? "");
    }
  }

  static div(text, ...vars) {
    const element = document.createElement("div");

    Elements.append(text, vars, element);

    return element;
  }

  static button(text, ...vars) {
    const element = document.createElement("button");

    Elements.append(text, vars, element);

    return element;
  }

  static input(text, ...vars) {
    const element = document.createElement("input");

    Elements.append(text, vars, element);

    return element;
  }
}

상태값

기본적으로 상태를 생성하여 사용할 수도 있고 singleton 패턴을 적용하여 전역 상태로서 사용할 수도 있게 선언하였다. 의존성을 가지고 있는 컴포넌트를 setDependents라는 함수를 통해 받아와서 값이 변하는 함수가 호출될 때마다 render될 수 있도록 하였다.

export class PageData {
  num = 0;

  setDependents(...dependents) {
    this.dependents = dependents;
  }

  render() {
    for (const dependent of this.dependents) {
      dependent.render();
    }
  }

  setNum(num) {
    this.num = num;
    this.render();
  }

  reset() {
    this.num = 0;
    this.render();
  }

  static #state = new PageData();
  static get state() {
    return this.#state;
  }
}

컴포넌트

각 컴포넌트 안에는 i와 render 메소드를 구현한다.
i는 그냥 짧게 자신을 나타내는 느낌을 주고 싶어서 써봤다.
i 안에서 element에 필요한 값들과 이벤트들을 설정한다. 호출될 때마다 static element를 갱신한다.
render는 replaceWith 함수를 활용하여 해당 컴포넌트의 의존성을 지니는 데이터가 바뀌면 해당 컴포넌트만 바꿔주도록 하였다.

import { Elements } from "../../element/index.js";
import { PageData } from "../../data/index.js";

export class PageText {
  static element = Elements.div``;

  static i() {
    const self = PageText;
    self.element = Elements.div`
        number is ${PageData.state.num}
      `;

    return self.element;
  }

  static render() {
    PageText.element.replaceWith(PageText.i());
  }
}

export class PageInput {
  static element = Elements.input``;

  static i() {
    const self = PageInput;
    self.element = Elements.input``;
    self.element.value = PageData.state.num;
    self.element.addEventListener("change", self.onChange);

    return self.element;
  }

  static onChange() {
    const value = Number(PageInput.element.value);
    PageData.state.setNum(value);
  }

  static render() {
    PageInput.element.replaceWith(PageInput.i());
  }
}

export class PageAddButton {
  static element = Elements.button``;

  static i() {
    const self = PageAddButton;
    self.element = Elements.button`add`;
    self.element.addEventListener("click", self.onClick);

    return self.element;
  }

  static onClick() {
    PageData.state.setNum(PageData.state.num + 1);
  }
}

export class PageResetButton {
  static element = Elements.button``;

  static i() {
    const self = PageResetButton;
    self.element = Elements.button`reset`;
    self.element.addEventListener("click", self.onClick);

    return self.element;
  }

  static onClick() {
    PageData.state.reset();
  }
}

페이지

컴포넌트 사용법은 간단하다.
각각의 컴포넌트를 불러와서 해당되는 위치에 넣어준다.
컴포넌트에 컴포넌트를 중첩하는 것도 가능하고 중간에 원하는 문자를 넣어줄 수도 있다.
상태값들은 PageData에서 관리하는데 상태값이 바뀔 때 render될 수 있는 컴포넌트들을 넣어준다.

import { Elements } from "../../element/index.js";
import { PageData } from "../../data/index.js";
import {
  PageText,
  PageInput,
  PageAddButton,
  PageResetButton,
} from "./component.js";

export class Page {
  static i() {
    PageData.state.setDependents(PageText, PageInput);

    return Elements.div`
      ${PageText.i()}
      ${PageInput.i()}
      ${PageAddButton.i()}
      ${PageResetButton.i()}
    `;
  }
}

개선할 점

뻘짓이긴한데 여러 관점으로 생각해볼 점이 많아서 재밌었다.

  • 컴포넌트 별로 스타일 추가
  • 차후에 shadowDOM과 함께 적용
  • 보일러 플레이트 줄이는 방향으로 개선 필요
  • 컴포넌트, 비즈니스 로직, 상태값 상호 의존 해결

0개의 댓글