지금까지는 HTML elements의 Attributes만을 적용시킬 수 있게 구현했었는데 React를 사용하면 다음과 같이 이벤트도 등록시킬 수 있다. 그래서 React와 비슷하게 오늘은 Element에 이벤트 등록을 하여 이벤트가 발생했을 때 등록한 함수가 호출되도록 기능을 추가하려고 한다.
// https://reactjs-kr.firebaseapp.com/docs/handling-events.html
function ActionLink() {
function handleClick(e) {
e.preventDefault();
console.log('The link was clicked.');
}
return (
<a href="#" onClick={handleClick}>
Click me
</a>
);
}
Element 요소에 등록할 이벤트를 소문자 대신 on으로 시작하여 camelCase로 작성하고 이벤트 발생시 전달할 함수를 작성해주면 된다.
Dj.render(<Test />, root);
class Test extends Dj.Component {
constructor(props: AttributeType){
super(props);
}
handleClick(event: MouseEvent) {
console.log('target:', event.target);
console.log('this', this);
}
render() {
return (
<div onClick={this.handleClick}>
<div>책목록</div>
<ul>
{books.map((book) => (
<li>{book.name}</li>
))}
</ul>
{false && <div>hidden</div>}
<fragment>
<div>fragment child</div>
</fragment>
</div>
)
}
}
jsx문법에서 이벤트를 작성하면 알아서 등록되는 것이 아니기 때문에 이제 vDOM 객체를 활용하여 생성할 Node에 이벤트를 수동으로 등록해줘야 한다.
그런데 지금까지 작성해왔던 코드를 보면 생성할 Node에 사용자가 정의한 attribute등을 등록하는 역할을 해온 함수가 updateNode()라는 함수이기 때문에 마찬가지로 해당 함수에서 작성한 이벤트를 등록해주는 코드를 추가해주면 된다.
// 기존의 코드
function updateNode(newNode: HTMLElement, vDOM: IDom){
// 이벤트나 속성 처리
Object.entries(vDOM.attributes || {}).forEach(([key, value]) => {
newNode.setAttribute(key, value);
});
}
이벤트를 작성해줄 때 on이라는 prefix를 붙여줬기 때문에 /^on/.test(key)
또는 key.startsWith('on')
를 사용하여 on으로 시작되는 key값을 잡아내어 이벤트를 등록해주는 코드를 작성해주면 된다. (React에서도 일반 속성과 구별하기 위해 on이라는 prefix를 붙여주지 않았을까라는 생각이 든다.)
function updateNode(newNode: HTMLElement, vDOM: IDom){
// 이벤트나 속성 처리
Object.entries(vDOM.attributes || {}).forEach(([key, value]) => {
if(key.startsWith('on')){
const eventType = key.slice(2).toLocaleLowerCase();
newNode.addEventListener(eventType, value);
return;
}
newNode.setAttribute(key, value);
});
}
일단 등록한 이벤트 타입이 발생했을 때 작성한 함수가 호출되는 것을 확인할 수 있었다.
하지만 문제점이 있는데 this가 가리키는 주체가 이벤트를 단 Element를 가리키고 있는 것이다. React에서는 일반적으로 this가 해당 컴포넌트를 가리켜 이벤트가 발생했을 때 컴포넌트의 메서드 setState()등을 활용하는데 이렇게 되면 직접적으로 DOM 조작을 할 수 있는 위험성이 생긴다.
React는 상태를 기반으로 DOM을 업데이트하는데 직접적으로 DOM 조작을 한다면 테스트 및 디버깅하기가 어려워질 가능성이 있다.
그래서 이를 해결하려면 bind() 메서드를 사용하여 명시적으로 바인딩을 해주거나 arrow function방식으로 변경해줘야 한다.
class Test extends Dj.Component {
constructor(props: AttributeType){
super(props);
// bind하는 코드 추가
this.handleClick = this.handleClick.bind(this);
}
// 생략...
}
이제 의도했던 방향과 맞게 정상적으로 동작하는 것을 확인할 수 있다.