최종적으로 컴포넌트 템플릿 구조를 완성했다
파라미터로 받아온 부모 돔에 innerHTML로 템플릿을 생성하고, 부모 돔에 이벤트를 걸어주어 위임하는 방식으로 구현을 했었지만 여러 문제점이 있었다
모든 자식 컴포넌트는 부모 컴포넌트 첫 생성 시에 같이 생성되어 연결되고,
부모의 상태 변화에 영향을 받는 컴포넌트만 필요에 따라 재렌더링 시켜줄 수 있다
// 적용
class RandomQuotes extends Component<undefined, { [key: string]: string }> {
$contents;
$changeButton;
constructor($target: Element, tagName: string) {
super($target, tagName);
this.$contents = new Contents(this.$myDom, 'div', { ...this.state });
this.$changeButton = new ChangeButton(this.$myDom, 'button', {
onClick: this.handleClickButton.bind(this),
});
}
setup() {
this.state = {
quote: '',
author: 'RandomQuotes',
};
this.setSelector(this.$myDom, 'quotes-container');
}
setState(newState: { [key: string]: string }) {
this.state = { ...this.state, ...newState };
this.$contents.setState(newState);
}
async handleClickButton() {
const { data } = await axios.get('https://free-quotes-api.herokuapp.com/');
this.setState({ quote: data.quote, author: data.author });
}
}
export default RandomQuotes;
// 템플릿
export default class Component<P, S> {
$target: Element;
$myDom: Element;
props: P;
state?: S;
constructor($target: Element, tagName?: string, props?: P) {
this.$target = $target;
this.$myDom = document.createElement(tagName);
this.$target.appendChild(this.$myDom);
this.props = props;
this.setup();
this.setEvent();
this.render();
}
setup() {
return;
}
setSelector($dom: Element, name: string) {
if ($dom instanceof HTMLElement) {
$dom.dataset.name = name;
}
}
mounted() {
return;
}
template() {
return '';
}
render() {
this.$myDom.innerHTML = this.template();
this.mounted();
}
setEvent() {
return;
}
setState(newState: S) {
this.state = { ...this.state, ...newState };
this.render();
}
addEvent(eventType: string, callback: (e: Event) => void) {
this.$myDom.addEventListener(eventType, callback);
}
addEventToParent(
eventType: string,
selector: string,
callback: (e: Event) => void,
) {
const children = [...Array.from(this.$target.querySelectorAll(selector))];
const isTarget = (target: EventTarget): boolean | Element => {
if (target instanceof HTMLElement) {
return children.includes(target) || target.closest(selector);
}
};
this.$target.addEventListener(eventType, event => {
if (!isTarget(event.target)) return false;
callback(event);
});
}
}