컴포넌트 템플릿
이전에 리액트나 뷰를 이용할 때는 정해져 있는 형식이 있었기 때문에 편리했습니다. 반면에, Vanilla JS는 제가 자유롭게 작성할 수 있는 구조입니다. 기존 코드는 규칙도 이전에 작성한 코드를 보며 ctrl+c, v 방식으로 비슷하게 만들었습니다.
하지만 이는 비효율적인 방법으로 컴포넌트가 늘어날 수록 부담되는 방법입니다. 또, 가독성이 좋지 않기 때문에 다른 사람이 봤을 때 어떤 기능을 하는지, 어떤 방식으로 작성했는지 알 수 없는 문제가 있습니다.
그렇기 때문에 편리하게 형식을 항상 동일하게 가져갈 수 있도록 작성해야 할 필요성을 느껴 컴포넌트 템플릿을 만들어 사용했습니다.
export default class Component {
$target;
props;
state;
constructor({ $target, props }) {
this.$target = $target;
this.props = props;
this.setup();
this.render();
this.setEvent();
}
setup() {} //초기화 부분
mounted() {}
template() { // 만들어지기 원하는 구조를 작성
return "";
}
render() {
this.$target.innerHTML = this.template();
this.mounted();
}
setEvent() {}
setState(newState) {
this.state = { ...this.state, ...newState };
this.render();
}
addEvent(eventType, selector, callback) {
const children = [...this.$target.querySelectorAll(selector)];
this.$target.addEventListener(eventType, (e) => {
if (!e.target.closest(selector)) return false;
callback(e);
});
}
}
addEvent는 setEvent안에서 사용하면 되는데,
기존에 이벤트를 추가해주는 방식과 비슷하게 작성해 주면 됩니다.
Before
this.$sideBarPages.addEventListener("scroll", (e) => {
const scrollPositon = this.$sideBarPages.scrollTop;
const eventArea = document.querySelector(".sidebar__pages");
if (scrollPositon > 0) {
if (!eventArea.classList.contains("scrolled")) {
eventArea.classList.add("scrolled");
}
} else {
eventArea.classList.remove("scrolled");
}
});
}
After
setEvent() {
this.addEvent("scroll", ".sidebar__pages", (e) => {
const scrollPositon = this.$target.scrollTop;
const eventArea = document.querySelector(".sidebar__pages");
if (scrollPositon > 0) {
if (!eventArea.classList.contains("scrolled")) {
eventArea.classList.add("scrolled");
}
} else {
eventArea.classList.remove("scrolled");
}
});
}
적용 사례
Sidebar.js
Before
import { push } from "./router.js";
import Data from "./data.js";
import { SideBarHeader, SideBarPages } from "@components";
/**
* SideBar를 만들어주는 컴포넌트
*/
export default class SideBar {
constructor({ $target, initialState, editorsetState }) {
this.$target = $target;
this.$page = document.createElement("aside");
this.$page.className = "sidebar__aside--flex";
this.sideBarHeader = new SideBarHeader({ $target: this.$page });
new SideBarPages({ $target: this.$page, initialState, editorsetState });
this.$target.appendChild(this.$page);
}
}
After
import { Component } from "@core";
import { push } from "./router.js";
import { SideBarHeader, SideBarPages } from "@components";
export default class SideBar extends Component {
setup() {}
template() {
return `
<aside class="sidebar__aside--flex">
<section class="sidebar__header"></section>
<section class="sidebar__pages"></section>
</aside>
`;
}
mounted() {
const $sidebarHeader = this.$target.querySelector(".sidebar__header");
const $sidebarPages = this.$target.querySelector(".sidebar__pages");
new SideBarHeader({ $target: $sidebarHeader });
new SideBarPages({ $target: $sidebarPages, props: this.props });
}
}
SideBarHeader.js
Before
import { push } from "@/router";
import Data from "@/data";
import { setItem, getItem } from "@stores";
export default class SideBarHeader {
constructor({ $target }) {
this.$target = $target;
this.$sideBarHeader = document.createElement("section");
this.$sideBarHeader.className = "sidebar__header";
this.data = new Data();
this.$beforeSelected = 0;
this.initialize();
this.eventAdd();
}
initialize = () => {
this.$sideBarHeader.innerHTML = `
<span class = 'sidebar__header--main sidebar__header--action' data-action = 'main'>H의 Notion</span>
<div class = 'sidebar__header--container'>
<div class = 'sidebar__header--action' data-action = 'quick_start'>
<span class = "material-symbols-rounded">bolt</span>
<span>Quick start</span>
</div>
<div class = 'sidebar__header--action' data-action = 'guestbook'>
<span class = "material-symbols-rounded">menu_book</span>
<span>Guestbook</span>
</div>
<div class = 'sidebar__header--action' data-action = 'add'>
<span class = "material-symbols-rounded">edit_square</span>
<span>Add a page</span>
</div>
</div>`;
this.$target.appendChild(this.$sideBarHeader);
};
render = () => {
const selected = getItem("selected");
document
.querySelector(
`.sidebar__pages--detail[data-id="${this.$beforeSelected}"]`
)
?.classList.remove("highlight");
document
.querySelector(`.sidebar__pages--detail[data-id="${selected}"]`)
?.classList.add("highlight");
};
eventAdd = () => {
this.$sideBarHeader.addEventListener("click", this.clickEventAdd);
};
clickEventAdd = async (e) => {
if (e.target.closest(".sidebar__header--action")) {
const action = e.target.closest(".sidebar__header--action").dataset
.action;
switch (action) {
case "main":
push(`/`);
break;
case "add":
await this.data.addDocumentStructure().then((x) => {
push(`/${x.id}`);
this.$beforeSelected = getItem("selected");
setItem("selected", x.id);
this.render();
});
break;
case "quick_start":
push(`/quick_start`);
break;
case "guestbook":
push(`/guestbook`);
break;
}
}
};
}
After
import { push } from "@/router";
import Data from "@/data";
import { setItem, getItem } from "@stores";
import { Component } from "@core";
export default class SideBarHeader extends Component {
setup() {
this.data = new Data();
this.$beforeSelected = 0;
}
template() {
return `
<span class = 'sidebar__header--main sidebar__header--action' data-action = 'main'>H의 Notion</span>
<div class = 'sidebar__header--container'>
<div class = 'sidebar__header--action' data-action = 'quick_start'>
<span class = "material-symbols-rounded">bolt</span>
<span>Quick start</span>
</div>
<div class = 'sidebar__header--action' data-action = 'guestbook'>
<span class = "material-symbols-rounded">menu_book</span>
<span>Guestbook</span>
</div>
<div class = 'sidebar__header--action' data-action = 'add'>
<span class = "material-symbols-rounded">edit_square</span>
<span>Add a page</span>
</div>
</div>
`;
}
setEvent() {
this.addEvent("click", ".sidebar__header", async (e) => {
if (e.target.closest(".sidebar__header--action")) {
const action = e.target.closest(".sidebar__header--action").dataset
.action;
switch (action) {
case "main":
push(`/`);
break;
case "add":
await this.data.addDocumentStructure().then((x) => {
push(`/${x.id}`);
this.$beforeSelected = getItem("selected");
setItem("selected", x.id);
});
break;
case "quick_start":
push(`/quick_start`);
break;
case "guestbook":
push(`/guestbook`);
break;
}
}
});
}
}
두 사례 모두 가독성이 훨씬 향상된 것을 알 수 있습니다.
이 정도면 이전과 비교했을 때 처음 보는 사람도 이해하기 편하지 않을까 생각합니다.
'프로젝트 > Vanilla JS 문서편집기' 카테고리의 다른 글
[Vanilla JS 문서편집기 설명 및 개선] 6. 중앙 집중식 상태 관리 적용하기 (1) | 2024.09.04 |
---|---|
[Vanilla JS 문서편집기 설명 및 개선] 5. VirtualDOM처럼 렌더링하기 (1) | 2024.08.29 |
[Vanilla JS 문서편집기 설명 및 개선] 3. 사이드바 렌더링 최적화 및 성능 비용 절감 (0) | 2024.08.21 |
[Vanilla JS 문서편집기 설명 및 개선] 2. 서버 요청 로직 (0) | 2024.08.09 |
[Vanilla JS 문서편집기 설명 및 개선] 1. vite 번들러 추가 (0) | 2024.08.08 |