
HTML에서 이벤트를 작성하던 것과 비슷합니다. 아래는 HTML에서의 이벤트 입니다.
<button onclick="실행 코드">예시 버튼</button>
리액트에서의 이벤트는 어떨까요?
이벤트 이름은 카멜 표기법으로 작성
HTML: onclick
React: onClick
이벤트에 실행할 자바스크립트 코드를 전달하는 것이 아니라, 함수 형태의 값을 전달
HTML: 큰따옴표 안에 실행할 자바스크립트 코드
React: 함수 형태의 객체
DOM 요소에만 이벤트를 설정
<MyComponent onClick={doSomething}/> 위와 같이 직접 만든 MyComponent에 onClick 이벤트를 설정한다고 해서 해당 컴포넌트를 클릭 시 doSomething 함수가 실행되는 것이 아닌, onClick이라는 props에 doSomething 함수를 담아 MyComponent에 전달해 줄 뿐입니다.더 많은 이벤트는 리액트 메뉴얼을 참고 바랍니다.
자세한 실습 과정은 서적을 참고해주세요.
4.2.2.1 onChange 이벤트 설정
아래와 같이 컴포넌트를 만들고 개발자 도구를 열어 input 태그에 입력해보세요.
import React, { Component } from "react";
class EventPractice extends Component {
render() {
return (
<div>
<input
type="text"
name="message"
placeholder="아무거나 입력해 보세요"
onChange={(e) => {
console.log(e);
}}
/>
</div>
);
}
}
export default EventPractice;
onChange={(e) => {
console.log(e);
}}
위 코드로 인하여 아래와 같이 e 객체가 출력되는 것을 확인할 수 있습니다.

e 객체는 SyntheticEvent로 웹 브라우저의 네이티브 이벤트를 감싸는 객체입니다.
웹 브라우저의 네이티브 이벤트란 브라우저에서 기본적으로 제공하는 이벤트를 말합니다.
그렇다면 Chrome, Edge, Safari,, 등등 다양한 브라우저에서 이벤트 핸들링 방식이 동일할까요? 당연히 차이가 있고 브라우저 별로 각각 핸들링 처리를 해준다면 성능을 저하시킬 것 입니다.
이를 위해 리액트에서 SyntheticEvent 객체를 제공합니다.
SyntheticEvent는 브라우저 네이티브 이벤트를 추상화하여 브라우저 호환성을 높이고 브라우저 마다 다른 이벤트 이름이나 속성 등을 일관된 방식으로 처리할 수 있도록 도와줍니다.
또한 SyntheticEvent는 다음과 같은 기술을 통해 이벤트 처리의 성능을 최적화하고 효율적인 코드를 제공합니다.
이벤트 풀링(Event Pooling)
SyntheticEvent 객체는 이벤트가 처리되는 동안에만 유효하며, 완료되면 메모리에서 해제됩니다. 이 때 매번 이벤트 객체를 새로 생성하고 해제하는 것은 성능상의 문제가 있습니다.
이벤트 폴링은 미리 이벤트 객체를 생성해 두고, 이벤트 발생 때 마다 재활용함으로서 오버헤드를 줄일 수 있습니다.
예시로 setInterval 함수를 들 수 있습니다.
Pooling() {
this.answer = setInterval(this.checkState, 1000);
}
위와 같이 특정 state를 확인하기 위해 실제 이벤트 처리가 아닌 일정 시간 간격으로 checkState 함수를 호출하여 검사하는 방식으로 이벤트 폴링하게 됩니다.
하지만 위와 같은 방식은 일정 시간 간격으로 함수를 호출하기 때문에 CPU 자원을 낭비할 수 있으므로 필요한 경우에만 사용하는 것이 좋습니다.
이벤트 위임(Event Delegation)
이벤트 위임은 이벤트를 처리하기 위해 각각의 요소에 이벤트 리스너를 등록하는 것이 아니라, 상위 요소에 하나의 이벤트 리스너를 등록하여 처리하는 기술입니다.
예시로 다음의 코드를 봅시다.
function handleClick(event) {
console.log(event.target.innerText);
}
function App() {
return (
<ul id="myList" onClick={handleClick}>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
);
}
위와 같이 하면 각각 li 태그마다 핸들링을 해줄 필요 없이 상위 요소에 핸들링을 해주어 event.target을 통해 클릭된 요소를 확인 할 수 있습니다.
이를 통해 코드의 복잡성을 줄일 수 있습니다.
다시 돌아와서, SyntheticEvent는 이벤트가 끝나면 정보가 초기화 됩니다.
만약 비동기적으로 이벤트 객체를 참조할 일이 있다면 e.persist() 함수를 사용하거나 e.target.value를 참조하여 input값을 state에 기록해 두는 등의 처리를 해줘야 합니다.
e.target.value를 참조하는 법은 뒤에 다시 다루겠습니다.
class MyComponent extends React.Component {
// handleClick = (e) => {
// setTimeout(() => {
// console.log(e.type); // null이 출력됨
// }, 1000);
// };
handleClick = (e) => {
e.persist(); // SyntheticEvent 객체를 유지하도록 지시
setTimeout(() => {
console.log(e.type); // "click"이 출력
}, 1000);
};
render() {
return <button onClick={this.handleClick}>Click me</button>;
}
}4.2.2.2 state에 input 값 담기
input 입력 값을 state로 관리하는 테크닉을 배워 봅시다.
import React, { Component } from "react";
class EventPractice extends Component {
state = {
message: "",
};
render() {
return (
<div>
<input
type="text"
name="message"
placeholder="아무거나 입력해 보세요"
value={this.state.message} // 0, 3, 6: message값 출력
onChange={(e) => { // 1: input태그에 입력
this.setState({
message: e.target.value, // 2: 입력 값 state에 저장
});
}}
/>
<button
onClick={() => { // 4: 버튼 클릭
this.setState({
message: "", // 5: state값 ''으로 수정
});
}}
>
삭제
</button>
</div>
);
}
}
export default EventPractice;
위 과정을 그림으로 표현하면 다음과 같습니다.

지금까지 이벤트를 처리할 때 렌더링을 하는 동시에 함수를 만들어서 전달해 주었습니다.
이 방법 대신 함수를 미리 준비하여, 즉 외부에 있는 함수를 호출하는 방식도 있습니다. 성능상으로 차이가 거의 없지만, 가독성은 훨씬 높습니다. 때문에 해당 방식을 권장합니다.
4.2.3.1 기본 방식
import React, { Component } from "react";
class EventPractice extends Component {
state = {
message: "",
};
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.setState({
message: e.target.value,
});
}
render() {
return (
<div>
<input
type="text"
name="message"
placeholder="아무거나 입력해 보세요"
value={this.state.message}
onChange={this.handleChange}
/>
</div>
);
}
}
export default EventPractice;
함수가 호출될 때 this는 호출부에 따라 결정되므로 클래스의 임의 메서드가 특정 HTML 요소의 이벤트로 등록되는 과정에서 메서드와 this의 관계가 끊어져 버립니다.
→ 이벤트 핸들러는 일반적으로 전역에서 호출되기 때문에 메서드 내부의 this 값은 전역 객체를 가리키게 됩니다.
때문에 임의 메서드가 이벤트로 등록되어도 this가 컴포넌트 자신으로 가리키기 위해서는 메서드를 this와 바인딩하는 작업이 필요합니다. 만약 바인딩하지 않는다면 this가 undefined를 가리킵니다.
위 코드에선 constructor 함수에서 바인딩하는 작업이 이루어집니다.
4.2.3.2 Property Initializer Syntax를 사용한 메서드 작성
위 방법이 정석이긴 하지만 새 메서드를 만들 때마다 constructor도 수정해야 하기 때문에 불편합니다.
이것을 간단하게 하기 위해선 바벨의 transform-class-properties 문법을 사용하여 화살표 함수 형태로 메서드를 정의하면 됩니다.
import React, { Component } from "react";
class EventPractice extends Component {
state = {
message: "",
};
handleChange = (e) => {
this.setState({
message: e.target.value,
});
};
render() {
return (
<div>
<input
type="text"
name="message"
placeholder="아무거나 입력해 보세요"
value={this.state.message}
onChange={this.handleChange}
/>
</div>
);
}
}
export default EventPractice;
화살표 함수 내부에서 this 키워드를 사용하면 함수가 정의된 시점에서 상위 스코프의 this 값을 참조하기 때문에 자동으로 해당 클래스의 인스턴스를 가리키도록 바인딩이 이루어집니다.
때문에 화살표 함수를 사용하면 별도의 바인딩 과정도 생략할 수 있고 가독성 또한 높일 수 있습니다.
input 태그가 여러 개일 경우 각각 메서드를 만들어줘도 되지만 더 쉽고 가독성을 챙기면서 해결할 수 있습니다.
바로 e.target.name을 활용합니다. e.target.name은 input 태그의 name을 가리킵니다.
import React, { Component } from "react";
class EventPractice extends Component {
state = {
username: "",
message: "",
};
handleChange = (e) => {
this.setState({
[e.target.name]: e.target.value,
});
};
render() {
return (
<div>
<input
type="text"
name="username"
placeholder="사용자 명"
value={this.state.username}
onChange={this.handleChange}
/>
<input
type="text"
name="message"
placeholder="아무거나 입력해 보세요"
value={this.state.message}
onChange={this.handleChange}
/>
</div>
);
}
}
export default EventPractice;
위 코드에서 눈여겨보아야 할 점은 다음 코드입니다.
handleChange = (e) => {
this.setState({
[e.target.name]: e.target.value,
});
};
우선 name을 통해 태그를 특정하여 하나의 메서드로 여러개의 태그를 관리할 수 있다는 점이 핵심이고,
두번째로 Javascript는 객체 안에서 key를(변수를) [ ]로 감싸면 해당 변수 안에 있는 값이 key 값으로 적용 된다는 점입니다.
이번엔 키를 눌렀을 때 발생하는 KeyPress 이벤트를 처리하는 방법을 알아보겠습니다.
다음은 input 태그에서 입력을 하고 포커싱되어 있을 때 Enter을 눌렀을 시 동작하는 코드 입니다.
import React, { Component } from "react";
class EventPractice extends Component {
state = {
message: "",
};
handleChange = (e) => {
this.setState({
message: e.target.value,
});
};
handleKeyPress = (e) => {
if (e.key === "Enter") {
this.setState({
message: "",
});
}
};
render() {
return (
<div>
<input
type="text"
name="message"
placeholder="아무거나 입력해 보세요"
value={this.state.message}
onChange={this.handleChange}
onKeyPress={this.handleKeyPress}
/>
</div>
);
}
}
export default EventPractice;
서적에서는 onKeyPress를 알려주지만 onKeyPress는 브라우저 별 적용되는 부분이 통일되지 않고 최신 브라우저에서 onKeyDown 혹은 onKeyUp 이벤트로 대체되었기 때문에 권장하지 않는다고 합니다.
코드는 정상적으로 동작하는 것으로 보이지만 어떤 브라우저에서 문제가 생길지 모르기 때문에 지양하는 것이 좋습니다.
위 코드에서는 onKeyDown으로 대체할 수 있습니다.
다음은 지금까지 했던 실습은 함수 컴포넌트로 구현한 코드입니다.
import React, { useState } from "react";
const EventPractice = () => {
const [form, setForm] = useState({
username: "",
message: "",
});
const { username, message } = form;
const onChange = (e) => {
const nextForm = {
...form,
[e.target.name]: e.target.value,
};
setForm(nextForm);
};
const onKeyDown = (e) => {
if (e.key === "Enter") {
setForm({
username: "",
message: "",
});
}
};
return (
<div>
<input
type="text"
name="username"
placeholder="사용자 명"
value={username}
onChange={onChange}
/>
<input
type="text"
name="message"
placeholder="아무거나 입력해 보세요"
value={message}
onChange={onChange}
onKeyDown={onKeyDown}
/>
</div>
);
};
export default EventPractice;
리액트에서 이벤트를 다루는 것은 순수 Javascript 또는 jQuery를 사용한 이벤트를 다루는 것과 비슷합니다.
리액트의 장점 중 하나는 Javascript에 익숙하다면 쉽게 활용할 수 있다는 점이니 HTML DOM Event와 Javascript에 대해 잘 알고 있다면 리액트의 컴포넌트 이벤트도 쉽게 다룰 수 있을 것입니다.
지금까지 클래스형 컴포넌트와 함수 컴포넌트 모두 보았는데요. 후에는 함수 컴포넌트에서 유용한 커스텀 Hooks을 더욱 접할 수 있을 것 입니다.
개인적인 의견으로는 비교하면 비교할수록 클래스형 컴포넌트가 왜 뒷전으로 가는지 알게되는 것 같습니다.
특정 상황이 아니고서야 간결성, 가독성, 성능, 상태 변화 처리, 테스트 용이 등등 함수 컴포넌트의 장점이 훨씬 많기 때문에 실제 프로젝트를 할 때는 함수 컴포넌트 사용을 추천합니다.
출처 : 김민준, 『리액트를 다루는 기술』, 길벗(2019), p76-92.