React/[책] 리액트를 다루는 기술

4장. 이벤트 핸들링

spring_sunshine 2021. 7. 14. 15:56

4.1 리액트의 이벤트 시스템 

이벤트

: 사용자가 웹 브라우저에서 DOM 요소들과 상호 작용하는 것

 

1) 이벤트 사용 시 주의 사항

 

1. 이벤트 이름은 카멜 표기법으로 작성한다. (onclick → onClick)

2. 이벤트에 실행할 자바스크립트 코드를 전달하는 것이 아니라, 함수 형태의 값을 전달한다.

// HTML
<button onclick= "testHandler()">Test</button>
function testHandler() {
	alert('Hello');
}
// react
<button onClick= { () => {
	alert(this.state.message);
    }} 
>확인</button>

3. DOM 요소에만 이벤트를 설정할 수 있다. 

<MyComponent onClick = {doSomething}/>

→ MyComponent를 클릭할 때 doSomething 함수를 실행하는 것이 아니라, 이름이 onClick인 props를 MyComponent에게 전달해 주는 것이다.

 

2) 이벤트 종류 

이벤트 종류 : Clipboard, Touch, Composition, UI, Keyboard, Wheel, Focus, Media, Form, Image, Mouse, Animation, Selection, Transition... (리액트 메뉴얼을 참고하자!)


4.2 예제로 이벤트 핸들링 익히기

1) 컴포넌트 생성 및 불러오기 

 

EventPractice.js

import React, { Component } form 'react';

class EventPractice extends Component {
	render() {
		return (
        	<div>
            	<h1>이벤트 연습</h1>
            </div>
        );
    }
}

export default EventPractice;

 

App 컴포넌트에서 EventPractice를 불러와 렌더링 한다.

import React from 'react';
import EventPractice from './EventPractice';

const App = () => {
	return <EventPractice />;
};

export default App;

출력 결과 → 이벤트 연습


2) OnChange 이벤트 핸들링하기 

 

2.1) onChange 이벤트 설정 

 

EventPractice 컴포넌트에 input 요소를 렌더링하는 코드와 해당 요소에 onChange 이벤트를 설정하는 코드를 작성한다. 

import React, { Component } from 'react';

class EventPractice extends Componet {
	render() {
		return (
        	<div>
            	<h1>이벤트 연습</h1>
                <input type="text" name="message" placeholder="아무거나 입력해 보세요"
                 onChange={ (e) => {
                 	console.log(e);
                 }}
                /> 
            </div>
        );
    }
}

export default EventPractice;
  • 콘솔에 기록되는 e 객체는 SyntheticEvent로 웹브라우저의 네이티브 이벤트를 감싸는 객체이다. 네이티브 이벤트와 인터페이스가 같으므로 순수 자바스크립트에서 HTML 이벤트를 다룰 때와 똑같이 사용하면 된다.
  • SyntheticEvent는 네이티브 이벤트와 달리 이벤트가 끝나고 나면 이벤트가 초기화되므로 정보를 참조할 수 없다. 예를 들어 0.5초 뒤에 e 객체를 참조하면 e 객체 내부의 모든 값이 비워지게 된다.
  • 비동기적으로 이벤트 객체를 참조할 일이 있다면 e.persist() 함수를 호출해 주어야 한다. 

▶ 비동기적 처리: 작업이 순서대로 진행되는 것이 아니라 한번에 여러개가 진행되어 처리된다.

더보기

리액트에서 사용되고있는 SyntheticEvent는 객체 풀링 방식(Object Pooling)을 사용합니다. 매 이벤트마다 해당 객체 사용되는것에 대해서 성능상의 이유로 리액트에서는 Object Pooling을 사용함으로써 객체 생성 시간을 줄이고 GC에 대한 노출도 줄이며 메모리관리에 소비되는 시간을 줄이는 방식을 사용하고 있습니다. 

 


2.2) state에 input 값 담기

 

생성자 메서드인 constructor에서 state 초기값을 설정하고, 이벤트 핸들링 함수 내부에서 this.setState 메서드를 호출하여 state를 업데이트 해보자.

 

♣ e.target은 이벤트가 발생한 DOM인 input DOM을 가리키게 된다.

 

EventPractice.js

import React, { Component } from 'react';

class EventPractice extends Componet {

	state = {
		message: ''
    }
	render() {
		return (
        	<div>
            	<h1>이벤트 연습</h1>
                <input type="text" name="message" placeholder="아무거나 입력해 보세요" 
                value={this.state.message}
                 onChange={ (e) => {
                 	this.setState({
                    	message: e.target.value
                    })
                 }}
                /> 
            </div>
        );
    }
}

export default EventPractice;

2.3) 버튼을 누를 때 comment 값을 공백으로 설정 

 

클릭 이벤트가 발생하면 현재 comment 값을 메시지 박스로 띄운 후 comment 값을 공백으로 설정하였다.

import React, {Component} from 'react';

class EventPractice extends Component {
	render(){
		return (
			<div>
				<h1>이벤트 연습</h1>
				<input 
					(...)
				/>
				<button onClick={() => {
					alert(this.state.message);
					this.setState({
						message:'' // comment 값 공백으로 설정
					});
				}}
               >
               확인</button>
			</div>
		);
	}
}

export default EventPractice;

3) 임의 메서드 만들기 

 

"이벤트에 실행할 자바스크립트 코드를 전달하는 것이 아니라, 함수의 값을 전달해야한다."

#1) 이벤트를 처리할 때 렌더링을 하는 동시에 함수를 만들어서 전달

#2) 함수를 미리 준비하여 전달 (성능 상 큰 차이x, 가독성이 훨씬 높다)

 


3.1) 기본 방식 

 

EventPractice.js 

class EventPractice extends Component {
	
    ...
    
	constructor(props){
		super(props);
		this.handleChange = this.handleChange.bind(this);
		this.handleClick = this.handleClick.bind(this);
	}

	handleChange(e) {
		this.setState({
			message: e.target.value
		});
	}

	handleClick() {
		alert(this.state.message);
		this.setState({
			message:''
		});
	}

	...
    
}
  • 함수가 호출될 때 this는 호출부에 따라 결정되는 데, 클래스의 임의 메서드가 특정 HTML 요소의 이벤트로 등록되는 과정에서 메서드와 this의 관계가 끊어진다.
  • this를 컴포넌트 자신으로 제대로 가리키기 위해서는 메서드와 this를 바인딩하는 작업이 필요하다.
  • 바인딩하지 않는 경우라면 this는 undefined를 가리키게 된다. 

 


3.2) Property Initializer Syntax를 사용한 메서드 작성 

 

메서드 바인딩은 생성자 메서드에서 하는 것이 정석이지만, 새 메서드를 만들 때마다 constructor도 수정해야 하기 때문에 불편할 수 있다. 

→ 바벨의 transfrom-class-properties 문법을 사용하여 화살표 함수 형태로 메서드를 정의하기

 

EventPractice.js

import React, { Component } from 'react';

class EventPractice extends Component {

  ...

  handleChange=(e)=>{
      this.setState({
          message: e.target.value
      });
  }

  handleClick = () => {
      alert(this.state.message);
      this.setState({
          message:''
      });
  }

  ...

}

4)  input 여러 개 다루기 

input이 여러 개일 때는 바로 event 객체를 활용하여 e.target.name 값을 사용하면 된다. 

 

EventPractice.js - handleChange 함수

handleChange = e =>{
	this.setState({
		[e.target.name]: e.target.value
	});
};

→ 객체 안에서 key를 [ ]로 감싸면 그 안에 넣은 레퍼런스가 가리키는 실제 값이 key값으로 사용된다. 


5) onKeyPress 이벤트 핸들링

 

comment 인풋에서 enter 클릭 시 handleClick 메서드를 호출하도록 코드를 작성하자. 

 

EventPractice.js

handleClick = () =>{
	alert(this.state.username + ":"+ this.state.message);
	this.setState({
		username:'',
		message:''
	});
}

handleKeyPress = (e) =>{
	if(e.key === "Enter'){
		this.handleClick();
	}
}

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} 
         onKeyPress={this.handleKeyPress}
        />
        <button onCLick = {this.handleClick}>확인</button>
     </div>
	);
}

4.3 함수형 컴포넌트로 구현해 보기 

 

EventPractice.js

import React, {useState} from 'react';

const 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 = () =>{
		alert(username+': '+message);
		setUSername('');
		setMessage('');
	};
	const onKeyPress = e => {
		if(key==="Enter"){
			onClick();
		}
	};

  return(
      <div>
          <h1>이벤트 연습</h1>
          <input type="text" name="message" placeholder="아무거나 입력" value={message}
              onChange={onChangeMessage} onKeyPress={onKeyPRess}
          />
      ... 
      </div>
  );
};

export default EventPractice;

→ e.target.name을 활용하지 않고 onChange 관련 함수 두 개를 따로 만들어 주었다. 인풋의 개수가 많아지면 e.target.name을 활용하는 것이 더 좋을 수도 있다.

 

다음은 useState를 통해 사용하는 상태에 문자열이 아닌 객체를 넣는 경우이다. 

 

EventPractice.js

import React, {useState} from 'react';

const EventPractice = () =>{
	const [form, setForm] = useState({username:'', message:''});
	const {username, message} = from
	const onChange = e => {  
		const nextForm = {
				...form, // 기존의 form 내용을 복사
				[e.target.name]:e.target.value  // 원하는 값을 덮어 씌우기
			};
		setForm(nextForm);
	};
	const onClick = () =>{
		alert(username+': '+message);
		setForm({
			username:'', message:''
		});
	}	
	const onKeyPress = e => {
		if(e.key==="Enter"){
			onClick();
		}
	};

	return(
		<div>
			<h1>이벤트 연습</h1>
			<input type="text" name="message" placeholder="아무거나 입력해 보세요" value={message}
				onChange={onChangeMessage} onKeyPress={onKeyPRess}
			/>
		...
		</div>
	);
};

export default EventPractice;

4.4 정리

  1. 리액트에서 이벤트를 다루는 것은 순수 자바스크립트 또는 jQuery를 사용한 웹 애플리케이션에서 이벤트를 다루는 것과 비슷하다. 따라서 기존 HTML DOM Event를 알고 있다면 리액트의 컴포넌트 이벤트도 쉽게 다룰 수 있다.
  2. 클래스형 컴포넌트로 할 수 있는 대부분의 작업은 함수형 컴포넌트로도 구현할 수 있다.
  3. 함수형 컴포넌트에서 여러 개의 인풋 상태를 관리하기 위해 useState에서 form 객체를 사용하는 법을 배웠다.
  4. 8장에서 배울 useReducer와 커스텀 Hooks를 사용하면 이 작업을 훨씬 편하게 할 수 있다.