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()}
`;
}
}
뻘짓이긴한데 여러 관점으로 생각해볼 점이 많아서 재밌었다.