📗목차

이벤트 핸들링

사용자가 웹 브라우저에서 DOM 요소들과 상호 작용하는 것을 이벤트라고 한다.

import React, { useState } from 'react';

const Say = () => {
    const [ message, setMessage ] = useState('');
    const onClickEnter = () => setMessage('안녕하세요!');
    const onClickLeave = () => setMessage('안녕히 가세요!');
    
    const [ color, setColor ] = useState('black');
    return (
        <>
            <button onClick={onClickEnter}>입장</button>
            <button onClick={onClickLeave}>퇴장</button>
            <h1 style={{ color }}>{messge}</h1>
            <button style={{ color: 'red' }} onClick={() => setColor('red') }>빨간색</button>
            <button style={{ color: 'green '}} onClick={() => setColor('green')}>초록색</button>
            <button style={{ color: 'blue' }} onClick={() => setColor('blue')}>파란색</button>
        </>
    )
}

export default Say;

이벤트를 사용할 때 주의 사항

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

예를 들어 HTML의 onclick은 리액트에서는 onClick으로 작성해야 된다.

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

HTML에서 이벤트를 설정할 때는 큰따옴표 안에 실행할 코드를 넣었지만, 리액트에서는 함수 형태의 객체를 전달한다.
위의 코드처럼 화살표 함수 문법으로 함수를 만들어 바로 전달해도 되고, 렌더링 부분 외부에 미리 만들어서 전달해도 된다.

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

즉 div, button, input, form, span 등의 DOM 요소에는 이벤트를 설정할 수 있지만, 우리가 직접 만든 컴포넌트에는 이벤트를 자체적으로 설정할 수 없다.

// 예를 들어서 이런 식은 안 됨
<MyComponent onClick={doSomething}/>

하지만 전달받은 props를 컴포넌트 내부의 DOM 이벤트로 설정할 수는 있다.

<div onClick={this.props.onClick}>
  { /* (...) */ }
</div>

클래스형 컴포넌트로 이벤트 핸들링

1. onChange 이벤트 핸들링

다음 코드를 EventPractice 컴포넌트의 render 메서드에 작성해보자.

import React, { Component } from 'react';

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

export default EventPractice;

import React, { Component } from 'react';
import EventPractice from './EventPractice';

class App extends Component {
  render() {
    return <EventPractice />
  }
}

export default App;

EventPractice.js의 onChage 설정 부분 다시 보기

onChange={
  (e) => { console.log(e); }
}

여기서 콘솔에 기록되는 e 객체는 SyntheticEvent로 웹 브라우저의 네이티브 이벤트를 감싸는 객체이다.
SyntheticEvent로 및 네이티브 이벤트와 달리 이벤트가 끝나면 이벤트가 초기화되므로 정보를 참조할 수 없다. 예를 들어 0.5ch e 객체를 참조하면 e 객체 내부의 모든 값이 비워지게 된다.

만약 비동기적으로 이벤트 객체를 참조할 일이 있다면 e.persist() 함수를 호출해줘야 한다.

코드를 다음과 같이 수정해보자.
값이 바뀔 때마다 바뀌는 값을 콘솔에 기록한다.

EventPractice.js의 onChage 코드 수정

onChange={
  (e) => { console.log(e.target.value); }
}



2. state에 input 값 담기

4. 리액트 - 컴포넌트편에서 배운대로 생성자 메서드인 constructor에서 state 초기값을 설정하고, 이벤트 핸들링 함수 내부에서 this.setState 메서드를 호출하여 state를 업데이트 해보자. 그 다음에는 input의 value 값을 state에 있는 값으로 설정하자.

import React, { Component } from 'react';

class EventPractice extends Component {
  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;

코드를 저장하고 input에 아무것이나 입력해보고, 오류가 발생하지 않고 제대로 입력할 수 있다면 state에 텍스트를 잘 담은 것이다.

3. 바뀐 state 값을 버튼을 누를 때마다 출력

위에서 setState 함수를 이용하여 바뀐 state 값을 이용해보자.

인풋에 입력한 값이 state에 잘 들어갔는지, 인풋에서 그 값을 제대로 반영하는지 검증해보겠다.

import React, { Component } from 'react';

class EventPractice extends Component {
  state = { message: '' }
  
  render() {
    return (
      <div>
        <h1>이벤트 연습</h1>
        <input 
          (...) // 생략
    	  value={this.state.message}
          onChange={
            (e) => {
              this.setState({ message: e.target.value })
            }
          }
        />
        <button onClick={
          () => {
            alert(this.state.message);
            this.setState({ message: '' }); // 클릭 후 input 값 초기화
          }
  		}>확인</button>
	  </div>
	);
  }
}

export default EventPractice;



4. 임의 메서드 만들기

이벤트를 사용할 때 주의 사항에서 (2)번에 "이벤트에 실행한 자바스크립트 코드를 전달하는 것이 아니라, 함수 형태의 값을 전달한다."라고 하였다. 그렇기에 이벤트를 처리할 때 렌더링을 하는 동시에 함수를 만들어서 전달해 주었다. 이 방법 대신 함수를 미리 준비하여 전달하는 방법도 있다. (가독성이 훨씬 좋다.)

앞서 onChange와 onClick에 전달한 함수를 따로 빼내서 컴포넌트 임의 메서드를 만들어 보겠다.

import React, { Component } from 'react';

class EventPractice extends Component {
  state = { message: '' }

  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(e) {
    alert(this.state.message);
    this.setState({ message: '' });
  }

  render() {
    return (
      <div>
        <h1>이벤트 연습</h1>
        <input 
          type="text"
          name="message"
          placeholder="아무거나 입력해 보세요"
          value={this.state.message}
  		  onChange={this.handleChange}
        />
        <button onClick={this.handleClick}>확인</button>
	  </div>
	);
  }
}

export default EventPractice

현재 constructor 함수에서 함수를 바인딩하는 작업이 이루어지고 있다.
함수가 호출될 때 this는 호출부에 따라 결정되므로, 클래스의 임의 메서드가 특정 HTML 요소의 이벤트로 등록되는 과정에서 메서드와 this의 관계가 끊어져 버린다. 이 때문에 임의 메서드가 이벤트로 등록되어도 this를 컴포넌트 자신으로 제대로 가리키기 위해서는 메서드를 this와 바인딩 하는 작업이 필요하다. 만약 바인딩하지 않으면 this가 undefined를 가리키게 된다.

메서드 바인딩은 생성자 메서드에서 하는 것이 정석이지만 새 메서드를 만들 때마다 constructor도 수정해야 되기 때문에 불편하다. 이 작업을 더 간단하게 하는 방법을 알아보자.

바로 바벨의 transform-class-properties 문법을 사용하여 화살표 함수 형태로 메서드를 정의하는 것이다.
다음 코드로 확인해보자.

import React, { Component } from 'react';

class EventPractice extends Component {
  state = { message: '' }

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

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

  render() {
    return (
      <div>
        <h1>이벤트 연습</h1>
        <input 
          type="text"
          name="message"
          placeholder="아무거나 입력해 보세요"
          value={this.state.message}
  		  onChange={this.handleChange}
        />
        <button onClick={this.handleClick}>확인</button>
	  </div>
	);
  }
}

export default EventPractice

5. input 여러 개 다루기

input이 여러 개일 때는 어떻게 작업해야 될까? 메서드를 여러 개 만들어야 할까?
더 쉽게 처리하는 방법을 알아보자.
바로 event 객체를 활용하는 것인데, e.target.name 값을 사용하면 된다.
onChange 이벤트 핸들러에서 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 });
  }

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

  render() {
    return (
      <div>
        <h1>이벤트 연습</h1>
      	<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}
        />
        <button onClick={this.handleClick}>확인</button>
	  </div>
	);
  }
}

export default EventPractice

여기서는 다음 코드가 핵심인데,

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

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

예를 들어 다음과 같은 객체를 만들면

const name = 'variantKey';
const object = {
  [name]: 'value'
};

결과는 다음과 같다.

{
  'variantKey': 'value'
}

6. onKeyPress 이벤트 핸들링

이번에는 키를 눌렀을 때 발생하는 KeyPress 이벤트를 처리하는 방법을 알아보자.

import React, { Component } from 'react';

class EventPractice extends Component {
  state = {
    username: '',
    message: ''
  }

  handleChange = (e) => {
    this.setState({ [e.target.name]: e.target.value });
  }
  
  handleClick (e) => {
    alert(this.state.username + ': ' + this.state.message);
    this.setState({ 
      username: '',
      message: ''
    });
  }
  
  handleKeyPress = (e) => {
    if(e.key === 'Enter') 
      this.handleClick();
  }
  
  render() {
    return (
      <div>
        <h1>이벤트 연습</h1>
      	<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>
	);
  }
}

export default EventPractice      
  

두번째 텍스트 input에 텍스트를 입력하고 Enter를 눌러보자. handleClick 메서드가 실행된다.

함수형 컴포넌트로 이벤트 핸들링

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(e.key === 'Enter')
      onClick();
  }
  
  return (
    <div>
      <h1>이벤트 연습</h1>
      <input
        type="text"
        name="username"
        placeholder="사용자명"
        value={username}
        onChange={onChangeUsername}
      />
      <input 
        type="text"
        name="message"
        placeholder="아무거나 입력해 보세요"
        value={message}
        onChange={onChangeMessage}
        onKeyPress={onKeyPress}
      />
      <button onClick={onClick}>확인</button>
    </div>  	    
  );
}

위 코드에서는 e.target.name을 활용하지 않고 onChange 관련 함수 두 개를 따로 만들어 주었다.

input이 두 개밖에 없다면 이런 코드도 괜찮지만 개수가 많아지면 e.target.name을 활용하는 것이 좋을 수 있다.
이번에는 useState를 통해 사용하는 상태에 문자열이 아닌 객체를 넣어 보겠다.
다음 코드와 같이 수정해보자.

import React 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 onClick = () => {
    alert(username + ': ' + message);
    setForm({
      username: '',
      message: ''
    });
  }
  const onKeyPress = e => {
  	if(e.key === 'Enter')
      onClick();
  }
  
  return (
    <div>
      <h1>이벤트 연습</h1>
      <input
        type="text"
        name="username"
        placeholder="사용자명"
        value={username}
        onChange={onChangeUsername}
      />
      <input 
        type="text"
        name="message"
        placeholder="아무거나 입력해 보세요"
        value={message}
        onChange={onChangeMessage}
        onKeyPress={onKeyPress}
      />
      <button onClick={onClick}>확인</button>
    </div>  	    
  );
}

e.target.name 값을 활용하려면 위와 같이 useState를 쓸 때 input 값들이 들어 있는 form 객체를 사용해주면 된다.

참고문헌

김민준,「리액트를 다루는 기술 :실무에서 알아야 할 기술은 따로 있다!」, 길벗, 개정판[실은 2판] 2019 (개정판)

profile
프론트엔드 개발자입니다.

0개의 댓글