상세 컨텐츠

본문 제목

[리액트 스타터 3] 4장. 이벤트 핸들링

22-23/22-23 리액트 스타터 3

by 케이비이 2022. 10. 13. 10:00

본문

728x90

4.1 리액트의 이벤트 시스템

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

리액트의 이벤트 시스템 사용법은 일반 HTML에서 이벤트를 작성하는 것과 비슷한데, 주의해야 할 몇 가지 사항이 있습니다. 

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

 1. 이벤트 이름은 카멜 표기법으로 작성합니다. (예: onClick, onKeyUp)

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

  • HTML에서 이벤트를 설정할 때는 큰따옴표 안에 실행할 코드를 넣었지만, 리액트에서는 함수 형태의 객체를 전달합니다. 화살표 함수 문법으로 함수를 만들어 전달하거나, 렌더링 부분 외부에 미리 만들어서 전달해도 됩니다.

 3. DOM 요소(div, button, input, form, span 등)에만 이벤트를 설정할 수 있습니다. 직접 만든 컴포넌트에는 이벤트를 자체적으로 설정할 수 없습니다.

// 직접 만든 컴포넌트 MyComponent, 이벤트 설정 불가
<MyComponent onClick={doSomething}/>
// 그저 이름이 onClick인 props를 MyComponent에 전달하고 있음.

 그러나 전달받은 props를 컴포넌트 내부의 DOM 이벤트로 설정할 수 있습니다.

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

4.1.2 이벤트 종류

 리액트에서 지원하는 이벤트 종류는 다음과 같습니다.

  • Clipboard
  • Composition
  • Keyboard
  • Focus
  • Form
  • Mouse
  • Image
  • Animation

리액트에서 지원하는 이벤트의 종류는 더 다양한데, 리액트 메뉴얼(https://facebook.github.io/react/docs/events.html)을 참고하시길 바랍니다.

 

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

4.2.1 컴포넌트 생성 및 불러오기

4.2.1.1 컴포넌트 생성

 먼저 클래스형 컴포넌트를 작성하여 기능을 구현해 보겠습니다.

// EventPractice.js
import React, { Component } from 'react';
 
class EventPractice extends Component {
  render() {
    return (
      <div>
        <h1>이벤트 연습</h1>
      </div>
    );
  }
}
 
export default EventPractice;

 

4.2.1.2 App.js에서 EventPractice 렌더링

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

// App.js
import EventPractice from './EventPractice';
 
const App = () => {
  return <EventPractice />;
};
 
export default App;

렌더링된 화면

4.2.2 onChange 이벤트 핸들링하기

4.2.2.1 onChange 이벤트 설정

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

// EventPractice.js
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;

 그리고 웹 브라우저에서 크롬 개발자 도구를 열어 input 값을 확인해 봅니다. 여기서 콘솔에 기록되는 e 객체는 SyntheticEvent로 모든 브라우저에서 이벤트를 동일하게 처리하기 위해 웹 브라우저의 고유한 이벤트를 감싸는 객체입니다. 

SyntheticEvent는 이벤트가 끝나고 나면 재사용하기 위해 이벤트가 초기화되므로 정보를 참조할 수 없습니다. 만약 비동기적으로 이벤트 객체를 참조할 일이 있다면 e.persist() 함수를 호출해 주어야 합니다.

// EventPractice.js의 onChange 코드 수정
onChange={
  (e) => {
    console.log(e.target.value);
  }
}

콘솔에 e.target.value 기록

4.2.2.2 state에 input 값 담기

 이번에는 3장에서 배운 state에 input 값을 담아 보겠습니다. 생성자 메서드인 constructor에서 state 초깃값을 설정하고, 이벤트 핸들링 함수 내부에서 this.setState 메서드를 호출하여 state를 업데이트합니다. 그다음 input의 value 값을 state에 있는 값으로 설정합니다.

// EventPractice.js
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;

 

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

 input 요소 코드 아래쪽에 button을 하나 만들고, 클릭 이벤트가 발생하면 현재 comment 값을 메시지 박스로 띄운 후 comment 값을 공백으로 설정하겠습니다.

// EventPractice.js
import React, { Component } from 'react';
 
class EventPractice extends Component {
 
  state = {
    message: ''
  }
 
  render() {
    return (
      <div>
        <h1>이벤트 연습</h1>
        <input
          (...)
        />
        <button onClick={
          () => {
            alert(this.state.message);
            this.setState({
              message: ''
            });
          }
        }>확인</button>
      </div>
    );
  }
}
 
export default EventPractice;

메시지 박스

4.2.3 임의 메서드 만들기

 리액트에선 이벤트에 실행할 자바스크립트 코드를 전달하는 것이 아니라, 함수 형태의 값을 전달해야 합니다. 그렇기에 이벤트를 처리할 때 렌더링을 하는 동시에 함수를 만들어서 전달해 주었습니다. 이 방법 대신, 함수를 미리 준비하여 전달하는 방법도 있습니다. 

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

// EventPractice.js
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() {
    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;

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

현재 예문의 코드에서는 constructor 함수에서 함수를 바인딩하는 작업이 이루어지고 있습니다.

 

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

 위의 방식은 새 메서드를 만들 때마다 constructor를 수정해야 하기 때문에 불편하다고 느낄 수 있습니다. 바벨의 transform-class-properties 문법을 사용하여 화살표 함수 형태로 메서드를 정의한다면 더 간단하게 작업을 수행할 수 있습니다.

// EventPractice.js
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;

4.2.4 input 여러 개 다루기

 input이 여러 개일 때는 event 객체를 활용하면 쉽게 처리할 수 있습니다. e.target.name 값을 사용하면 됩니다. onChange 이벤트 핸들러에서 e.target.name은 해당 인풋의 name을 가리킵니다. 이 값을 사용하여 state를 설정하면 쉽게 해결할 수 있습니다. 

다음 코드에서는 render 함수에서 name 값이 username인 input을 렌더링 해 주었고, state 쪽에도 username이라는 값을 추가해 주었습니다. 그리고 handleChange도 조금 변경해 주었습니다.

// EventPractice.js
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;

input 2개 처리

여기서는 다음 코드가 핵심입니다.

// EventPractice.js의 handleChange 함수
handleChange = e => {
    this.setState({
      [e.target.name]: e.target.value
    });
};

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

4.2.5 onKeyPress 이벤트 핸들링

 이번에는 키를 눌렀을 때 발생하는 KeyPress 이벤트를 처리하는 방법을 알아보겠습니다. comment 인풋에서 Enter를 눌렀을 때 handleClick 메서드를 호출하도록 코드를 작성해 봅시다.

// EventPractice.js
import { Component } from 'react';

clss EventPractice extends Component {
  (...)
  
  handleKeyPress = (e) => {
      if (e.key === 'Enter') {
        this.handleClick();
      }
  }
  
  render() {
    return (
      <div>
        (...)
        <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;

 

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 (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>
  );
};
export default EventPractice;

 위의 코드는 함수 컴포넌트로 같은 기능을 구현한 코드입니다. 함수 컴포넌트를 활용할 때는 const 키워드 + 함수 형태로 선언해야 합니다.  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 } = form;
  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="username"
        placeholder="사용자명"
        value={username}
        onChange={onChange}
      />
      <input
        type="text"
        name="message"
        placeholder="아무거나 입력해 보세요"
        value={message}
        onChange={onChange}
        onKeyPress={onKeyPress}
      />
      <button onClick={onClick}>확인</button>
    </div>
  );
};
export default EventPractice;

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

 

Quiz

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

2. 이벤트 이름은 ( 카멜  ) 표기법으로 작성합니다.

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

4. ( SyntheticEvent  )는 고유한 이벤트와 달리 이벤트가 끝나고 나면 이벤트가 초기화되므로 정보를 참조할 수 없습니다.

5. 임의 메서드가 이벤트로 등록되어도 this가 컴포넌트 자신으로 제대로 가리키기 위해서는 메서드를 this와 ( 바인딩 )하는 작업이 필요합니다.

6. 인풋의 개수가 많을 때 이벤트 핸들러에서 ( e.target.name )은 해당 인풋의 name을 가리킵니다.

7. 객체 안에서 key를 ( [  ](대괄호) )로 감싸면 그 안에 넣은 레퍼런스가 가리키는 실제 값이 key 값으로 사용됩니다.

 

8. Enter 키를 눌렀을 때 handleClick 메서드가 실행되도록 input 요소를 수정하고 메서드를 작성하세요. 메서드 이름은 handleKeyPress로 정하세요.

// EventPractice.js
import { Component } from 'react';

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

export default EventPractice;

9. 다음 코드를 더 간단하게 만들어보세요. 

    힌트: handleChangeUsername과 handleChangeMessage 메서드를 [ ]를 사용하여 병합하세요.

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

class EventPractice extends Component {

  state = {
    username:'',
    message: ''
  }

  handleChangeUsername = (e) => {
    this.setState({
      username: e.target.value
    });
  }
  handleChangeMessage = (e) => {
    this.setState({
      message: 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.handleChangeUsername}
        />
        <input
          type="text"
          name="message"
          placeholder="아무거나 입력해 보세요"
          value={this.state.message}
          onChange={this.handleChangeMessage}
        />
        <button onClick={this.handleClick}>확인</button>
      </div>
    );
  }
}

export default EventPractice;

 

 

코드 작성 문제 정답

 8번 정답

<input 
  (...) 
  onKeyPress={this.handleKeyPress}
/>

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

9번 정답

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

Corner React Starter 3

Editor : 도담

728x90

관련글 더보기