[ProPro] component 개선 방식에 이어서 고민을 해보면서 web component를 사용해볼까란 생각도 했는데 그러면 너무 많은 부분이 바뀌게 될 것 같아 포기를 했다. 그래서 기존의 컴포넌트 형태로 추상화한 Core Component를 건드리게 되었다.
자바스크립트에서 자바와 같이 추상클래스 구현은 어렵지만 동일한 클래스 구조를 가질 수 있도록 하는 목적과 통일함으로써 유지보수성을 높이고자 Core Component를 만들어두었었다. 하지만 돌이켜보니 원래 의도와는 다르게 팀원들간의 코드가 통일이 되어있지 않고 불필요한 메서드가 존재해 사소하지만 이런 부분들을 수정해보는 작업을 해보았다.
class Component {
// 생략...
createDom = (tagName, attrs) => {
const $dom = document.createElement(tagName);
for (const [key, value] of Object.entries(attrs)) {
$dom[key] = value;
}
return $dom;
};
replaceElement = ($old, $new) => {
$old.parentElement.replaceChild($new, $old);
};
appendRoot = ($root, $new, isNav = false) => {
if (isNav) {
if ($root.childNodes[0]) $root.replaceChild($new, $root.childNodes[0]);
else $root.appendChild($new);
return;
}
if ($root.childNodes[1]) $root.replaceChild($new, $root.childNodes[1]);
else $root.appendChild($new);
};
}
다음과 같은 메서드들은 Element를 생성하고 수정하는데 쓰이는 목적이기 때문에 유틸함수로써 빼두는게 좋다고 생각하여 분리하였다.
이제 사용하고자 하는 컴포넌트에서 필요한 함수만 불러오면 된다.
import { createDom } from '../../utils/dom';
const logo = createDom('a', {
className: 'logo router',
href: '/',
});
render()메서드는 새로운 Element을 생성해주는 역할, 즉 새로운 컴포넌트를 생성해준다. 그런데 최상위 부모 컴포넌트 역할을 하는 페이지와 페이지가 아닌 컴포넌트에서 render()하는 방식이 다르다.
페이지의 경우에는 네비게이션 컴포넌트와 충돌을 피하기 위해 appendRoot라는 커스텀 함수를 사용해서 id="root"를 가진 Element 하위에 appendChild() 메서드를 사용하여 props로 전달받은 요소에 새로운 Element를 삽입한다.
export default class ProfilePage extends Component {
constructor(props) {
super(props);
// 생략...
this.$dom = this.createDom('div', {
className: 'profile-page-wrapper',
});
this.render();
}
// 생략 ...
render = () => {
this.$dom.innerHTML = `
<div class="container">
// 생략...
</div>
`;
this.appendRoot(this.props, this.$dom);
};
}
반면에 페이지가 아닌 컴포넌트에서는 innerHTML 프로퍼티를 사용하여 Element를 생성하고 해당 컴포넌트를 호출한 부모 컴포넌트에서 생성된 Element를 삽입하는 방식을 사용하고 있다.
// 부모 역할하는 컴포넌트 예시
export default class ProfilePage extends Component {
// 생략 ...
appendChild = () => {
const btns = this.$dom.querySelector('.clearfix');
this.updateBtn = new Button({
className: 'updateBtn',
buttonText: '수정 완료',
onClick: this.submitProfileData,
});
btns.appendChild(this.updateBtn.$dom);
}
// 자식 역할하는 컴포넌트 예시
export default class Button extends Component {
constructor(props) {
super(props);
this.$dom = this.createDom('button', {
className: props.className,
});
this.render();
}
render = () => {
this.$dom.innerHTML = `
<span>${this.props.buttonText}</span>
`;
};
// 생략...
}
그래서 이런 부분들을 통일해주고 싶어 컴포넌트를 생성할때 생성한 Element를 삽입해줄 사용자 지정 Element역할을 해주는 container를 전달해주기로 했다.
class CustomComponent {
constructor({ container, props }) {
this.container = container;
this.props = props;
}
// 생략...
}
그러면 Core 역할을 하는 component에서 container라는 이름의 property로 가지고 있어 container.innerHTML를 사용하여 삽입해주면 되기 때문에 모든 컴포넌트에서 동일한 Element 생성방식을 사용할 수 있게 된다.
그리고 처음에 innerHTML, appendChild을 섞어 구현했던 이유 중에는 innerHTML 특성상 삽입해버린 내용으로 덮어씌워버리기 때문에 각 페이지의 렌더링 요소와 네비게이션 요소를 분리시켜주었다.