바닐라 자바스크립트로 토이 프로젝트를 진행하는 중에 모달을 보여주는 함수의 위치에 대해 고민이 되었다. 현재 프로젝트에서는 React와 유사한 함수형 패러다임을 적용했고, 컴포넌트 함수는 오직 컴포넌트 요소만을 반환하도록 설계했다. 하지만 이런 일관된 구조를 유지하려다 보니 막상 컴포넌트의 동작을 제어하는 함수는 어디에 위치해야할지가 애매했다.
'openModal' 함수를 실제로 이벤트가 발생하는 'Header.js' 컴포넌트에 위치시켰다. 하지만 이 방법은 openModal 기능이 여러 곳에서 재사용될 가능성을 감안하면 그리 이상적이지 않다.
// Header.js
import Modal from './modal/Modal';
const openModal = () => {
const modal = Modal();
document.querySelector('body').prepend(modal);
setTimeout(() => modal.classList.add(CLASSNAME_VISIBLE), 20);
}
const Header = () => {
const header = document.createElement('header');
header.className = 'header';
header.innerHTML = generateSkeleton();
const addPostBtn = header.querySelector('#add-post-btn');
addPostBtn.addEventListener('click', openModal);
return header;
};
export default Header;
// Modal.js
const Modal = () => {
const modal = document.createElement('div');
modal.className = 'modal';
modal.appendChild(CloseModalBtn());
return modal;
};
export default Modal;
'openModal' 함수를 모달 컴포넌트 'Modal.js'에 위치시켰다.
이것도 나쁘다곤 할 순 없지만, 'Modal.js'에서 Modal 컴포넌트 함수가 아닌 해당 컴포넌트를 제어하는 함수가 export 되는 것이기 때문에 내가 원했던 일관성을 해친다.
// Header.js
import {openModal} from './modal/Modal';
const Header = () => {
const header = document.createElement('header');
header.className = 'header';
header.innerHTML = generateSkeleton();
const addPostBtn = header.querySelector('#add-post-btn');
addPostBtn.addEventListener('click', openModal);
return header;
};
export default Header;
// Modal.js
const Modal = () => {
const modal = document.createElement('div');
modal.className = 'modal';
modal.appendChild(CloseModalBtn());
return modal;
};
export const openModal = () => {
const modal = Modal();
document.querySelector('body').prepend(modal);
setTimeout(() => modal.classList.add(CLASSNAME_VISIBLE), 20);
}
마지막으로 고려한 방법은 Modal 컴포넌트 함수에 open 이라는 메서드를 추가하는 것이었다. 이 방법을 사용하면 Modal.js에서 해당 컴포넌트를 export 하는 동시에, 컴포넌트는 '모달 열기' 기능을 자체적으로 가지게 되어 이전에 생겼던 문제를 해결할 수 있다.
// Header.js
import Modal from './modal/Modal';
const Header = () => {
const header = document.createElement('header');
header.className = 'header';
header.innerHTML = generateSkeleton();
const addPostBtn = header.querySelector('#add-post-btn');
addPostBtn.addEventListener('click', Modal.open);
return header;
};
export default Header;
// Modal.js
const Modal = () => {
const modal = document.createElement('div');
modal.className = 'modal';
modal.appendChild(CloseModalBtn());
return modal;
};
Modal.open = () => {
const modal = Modal();
document.querySelector('body').prepend(modal);
setTimeout(() => modal.classList.add(CLASSNAME_VISIBLE), 20);
};
export default Modal;
세 번째 방법, 함수 객체에 메서드를 추가하는 방식을 택함으로써 함수의 역할이 명확해지고, 코드의 일관성을 유지할 수 있었다.
이 방식은 함수 객체에 메서드를 추가하는 것이기 때문에, 이 함수를 새로 생성할 때마다 이 메서드도 복제되어 메모리 사용량이 증가할 수 있다는 단점이 있다. 지금과 같은 작은 프로젝트에서는 무시할 수 있는 수준이지만 프로젝트 규모가 커지면 충분히 고려해야할 부분이다. 또한 이런 패턴을 모든 모듈에 적용하면 코드가 복잡해질 수 있고 가독성이 떨어질 수 있기 때문에 최선의 방식은 아닐 수 있다. 그럼에도 위에 언급한 일관성을 유지한 것에 만족하고 새로운 방법을 시도해봤다는 것에도 의의를 두고 싶다.
너무 지엽적인 고민이었을까?
그래도 이런 작은 것들이 모여 전체적인 프로젝트 구조와 유지 보수성, 코드의 가독성에 큰 영향을 미칠거라고 생각한다.
이번 고민을 통해 조금이라도 나은 코드를 작성할 수 있게 되었기를.