import Component from "../core/Component.js";
export default class Items extends Component {
setup () {
this.$state = { items: ['item1', 'item2'] };
}
template () {
const { items } = this.$state;
return `
<ul>
${items.map((item, key) => `
<li>
${item}
<button class="deleteBtn" data-index="${key}">삭제</button>
</li>
`).join('')}
</ul>
<button class="addBtn">추가</button>
`
}
setEvent () {
this.$target.querySelector('.addBtn').addEventListener('click', () => {
const { items } = this.$state;
this.setState({ items: [ ...items, `item${items.length + 1}` ] });
});
this.$target.querySelectorAll('.deleteBtn').forEach(deleteBtn =>
deleteBtn.addEventListener('click', ({ target }) => {
const items = [ ...this.$state.items ];
items.splice(target.dataset.index, 1);
this.setState({ items });
}))
}
}
export default class Items extends Component {
setup () {/* 생략 */}
template () { /* 생략 */}
setEvent () {
// 모든 이벤트를 this.$target에 등록하여 사용하면 된다.
this.$target.addEventListener('click', ({ target }) => {
const items = [ ...this.$state.items ];
if (target.classList.contains('addBtn')) {
this.setState({ items: [ ...items, `item${items.length + 1}` ] });
}
if (target.classList.contains('deleteBtn')) {
items.splice(target.dataset.index, 1);
this.setState({ items });
}
});
}
}
이벤트 버블링을 사용하면 훨씬 직관적으로 처리할 수 있는데, 다만 기존의 setEvent
는 render를 할 때 마다 실행하기 때문에 core/Component.js에 라이프 사이클을 변경해야 한다.
export default class Component {
$target;
$state;
constructor ($target) {
this.$target = $target;
this.setup();
+ this.setEvent(); // constructor에서 한 번만 실행한다.
this.render();
}
setup () {};
template () { return ''; }
render () {
this.$target.innerHTML = this.template();
- this.setEvent();
}
setEvent () {}
setState (newState) {
this.$state = { ...this.$state, ...newState };
this.render();
}
}
이는 event를 각각의 하위 요소가 아니라 component의 target 자체에 등록하는 것이고, 따라서 component가 생성되는 시점에만 이벤트 등록을 해두면 추가로 등록할 필요가 없어진다.
이벤트 버블링을 통한 등록 과정을 메소드로 만들어 사용하면 코드가 더 깔끔해진다.
export default class Component {
$target;
$state;
constructor ($target) { /* 생략 */ }
setup () { /* 생략 */ }
template () { /* 생략 */ }
render () { /* 생략 */ }
setEvent () { /* 생략 */ }
setState (newState) { /* 생략 */ }
addEvent (eventType, selector, callback) {
const children = [ ...this.$target.querySelectorAll(selector) ];
// selector에 명시한 것 보다 더 하위 요소가 선택되는 경우가 있을 땐
// closest를 이용하여 처리한다.
const isTarget = (target) => children.includes(target)
|| target.closest(selector);
this.$target.addEventListener(eventType, event => {
if (!isTarget(event.target)) return false;
callback(event);
})
}
}
export default class Items extends Component {
setup () { /* 생략 */ }
template () {/* 생략 */ }
setEvent () {
this.addEvent('click', '.addBtn', ({ target }) => {
const { items } = this.$state;
this.setState({ items: [ ...items, `item${items.length + 1}` ] });
});
this.addEvent('click', '.deleteBtn', ({ target }) => {
const items = [ ...this.$state.items ];
items.splice(target.dataset.index, 1);
this.setState({ items });
});
}
}