리액트에서 이벤트를 다루는 방법에 대해 알아보려고 한다.
이벤트란 사용자가 웹 브라우저에서 DOM 요소들과 상호 작용하는 것
HTML을 사용해봤다면 이벤트를 다루는 것도 익숙할 것이다.
<button onclick="alert('executed')">
Click
</button>
HTML에서는 위 예시처럼 이벤트를 실행하면 " " 사이에 있는 JS를 실행하도록 코드를 작성한다.
그렇다면 리액트에서는 어떻게 이벤트를 다룰까?
리액트의 이벤트 시스템은 HTML 이벤트와 사용법이 비슷하지만 주의해야할 것이 몇 개 존재한다.
이벤트 이름은 카멜 표기법으로
onclick 같은 경우 onClick으로 작성해야 하며, onkeyup은 onKeyUp으로 작성해야 한다.
이벤트에 실행할 JS 코드를 전달하는 것이 아닌, 함수 형태의 값을 전달
큰 따옴표 안에 실행할 코드를 넣은 HTML과 달리 리액트에서는 함수 형태의 객체를 전달한다.
DOM 요소에만 이벤트 설정 가능
div, span, button 등의 DOM 요소에는 이벤트를 설정할 수 있지만, 직접 만든 컴포넌트에는 이벤트를 자체적으로 설정할 수 없다.
전달받은 props를 컴포넌트 내부의 DOM 이벤트로 설정할 수는 있다.
<div onClick={this.props.onClick}> </div>
리액트에서 지원하는 이벤트 종류에 대해 알아보고 싶다면 이곳을 참고하자
5개의 단계를 통해 이벤트 핸들링을 익혀보려고 한다.
컴포넌트 생성 및 불러오기
컴포넌트를 생성하고 불러오는 것은 이제 다들 익숙할거라 생각하고 생략하도록 하겠다.
onChange 이벤트 핸들링하기
onChange={
(e) => {
console.log(e);
}
}
위 예시에서 e 객체는 SyntheticEvent로 웹 브라우저의 네이티브 이벤트를 감싸주는 객체다.
SyntheticEvent는 흔히 사용하는 stopPropagation 과 preventDefault 를 포함하여 네이티브 이벤트와 동일한 인터페이스를 가지고 있다.
SyntheticEvent는 네이티브 이벤트와 달리 이벤트가 끝나고 나면 이벤트가 초기화되므로 정보를 참조할 수 없다.
👉 비동기적으로 이벤트 객체를 참조할 일이 있다면 e.persist( ) 함수를 호출해 줘야 한다.
onChange={
(e) => {
console.log(e.target.value);
}
}
이렇게 코드를 작성하면 값이 바뀔 때마다 바뀌는 값을 참조할 수 있다.
함수가 호출될 때 this는 호출부에 따라 결정되기 때문에, 클래스의 임의 메서드가 특정 HTML 요소의 이벤트로 등록되는 과정에서 메서드와 this의 관계가 끊어진다.
this가 컴포넌트 자신으로 제대로 가리키기 위해서는 메서드와 this를 바인딩 해줘야 함
바인딩 해주지 않으면 this가 undefined를 가리키게 됨
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 (
<input
vlaue={this.state.message}
onChange={this.handleChange}
/>
);
}
따라서 constructor 메서드에서 바인딩하는 작업을 해준 것을 볼 수 있다.
메서드 바인딩은 기존 방식처럼 생성자 메서드에서 하는 것이 정석이다.
하지만, 새로운 메서드를 만들 때마다 constructor을 수정해야하는 불편함을 해소하기 위해 바벨의 transform-class-properties 문법을 사용하는 방법이 있다.
👉 화살표 함수 형태로 메서드를 정의하는 것을 의미한다.
class EventPractice extends Component {
state = {
message : ''
}
handleChange = (e) => {
this.setState({
message: e.target.value
});
}
render() {
return (
<input
vlaue={this.state.message}
onChange={this.handleChange}
/>
);
}
기존 방식으로 구현한 것보다 훨씬 깔끔해진 것을 볼 수 있다.
그럴 수도 있겠지만 event 객체를 활용하면 더 쉽게 처리할 수 있다.
class EventPractice extends Component {
state = {
message : '',
username: ''
}
handleChange = (e) => {
this.setState({
[e.target.name]: e.target.value
});
}
render() {
return (
<>
<input
vlaue={this.state.username}
name="username"
onChange={this.handleChange}
/>
<input
vlaue={this.state.message}
name="message"
onChange={this.handleChange}
/>
</>
);
}
위 예시에서 handleChange 메서드의 코드를 살펴보자
해당 코드처럼 객체 안에서 key를 [ ]로 감싸면 그 안에 넣은 레퍼런스가 가리키는 실제 값이 key 값으로 사용된다.
const name = 'variantKey';
const object = {
[name]: 'value'
};
즉 위 예시는 { 'variantKey' : 'value' }와 같다.
class EventPractice extends Component {
state = {
message : '',
username: ''
}
handleChange = (e) => {
this.setState({
[e.target.name]: e.target.value
});
}
handleClick = () => {
this.setState({
username: '',
message: ''
});
}
handleKeyPress = (e) => {
if(e.key === 'Enter') {
this.handleClick();
}
}
render() {
return (
<input
vlaue={this.state.username}
name="username"
onChange={this.handleChange}
onKeyPress={this.handleKeyPress}
/>
);
}
지금까지 구현한 예시를 함수 컴포넌트로 바꿀 수 있다.
import { useState } from 'react';
export default function EventPractice = () => {
const [username, setUsername] = useState('');
const [message, setMessage] = useState('');
const onChangeUsername = e => setUsername(e.target.value);
const onChangeMessage = e => setMessage(e.target.value);
const onClick = () => {
setUsername('');
setMessage('');
};
const onKeyPress = e => {
if (e.key === 'Enter') {
onClick();
}
};
return (
<>
<input
vlaue={username}
name="username"
onChange={onChangeUsername}
onKeyPress={onKeyPress}
/>
<input
vlaue={message}
name="message"
onChange={onChangeMessage}
/>
</>
);
input이 위 예시처럼 두 개밖에 없는 경우 e.target.name을 활용하는 것보다 저렇게 함수 두 개를 따로 만들어 주는것도 괜찮다.
하지만 input의 개수가 많아진다면 e.target.name을 활용하는 것이 더 좋다.
e.target.name 값을 활용하기 위해서는 useState를 사용할 때 input 값들이 들어있는 form 객체를 만들어 사용하면 된다.
import { useState } from 'react';
export default function 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 onClick = () => {
setForm({
username:'',
message:''
};
const onKeyPress = e => {
if (e.key === 'Enter') {
onClick();
}
};
return (
<>
<input
vlaue={username}
name="username"
onChange={onChange}
onKeyPress={onKeyPress}
/>
<input
vlaue={message}
name="message"
onChange={onChange}
/>
</>
);
리액트에서 이벤트를 다루는 것은 순수 JS 또는 jQuery를 사용한 웹 애플리케이션에서 이벤트를 다루는 것과 비슷하다.
👉 JS에 익숙하면 쉽게 리액트를 활용할 수 있다.
기존 HTML DOM Event를 알고 있다면 리액트의 컴포넌트 이벤트도 쉽게 다룰 수 있을 것임
클래스형 컴포넌트로 할 수 있는 대부분의 작업은 함수 컴포넌트로도 구현 가능하다.
후에 공부할 useReducer와 커스텀 Hooks를 사용하면 더 편하게 할 수 있다.