Front-End

이벤트 핸들링 본문

리액트

이벤트 핸들링

jeongsso 2023. 2. 20. 20:48

자바스크립트에서도 이벤트가 있었는데,

리액트에서도 이벤트 핸들링이 있다.

한번 알아보자!

 

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

예를 들어 버튼에 마우스 커서를 올렸을 때는 onmouseover 이벤트를 실행하고,

클릭했을 때는 onclick 이벤트를 실행한다.

 

Form 요소는 값이 바뀔 때 onchange 이벤트를 실행합니다.

HTML을 사용한 적이 있다면 매우 익술할 것입니다!!

 

HTML에서 DOM요소에 이벤트를 설정하는 방법을 다시 살펴보자.

 

 

 

-    1.  리액트의 이벤트 시스템    -

리액트의 이벤트 시스템은 웹 브라우저의 HTML 이벤트와 인터페이스가 동일하기 때문에 사용법이 비슷합니다.

// Say.js
import { useState } from 'react';

const Say = () => {
  const [message, setMessage] = useState('');
  const onClickEnter = () => setMessage('안녕하세요');
  const onClickLeave = () => setMessage('안녕히 가세요');
  
  const [color, setColor] = useState('black');
  
  return (
    <div>
      <button onClick = { onClickEnter }> 입장 </button>
      <button onClick = { onClickLeave }> 퇴장 </button>
        (...)

사용법은 비슷한데,

주의할 것이 몇가지 있습니다.

 

 

 

=   (1)  이벤트를 사용할 때 주의 사항   =

 

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

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

또 onkeyup은 onKeyUp으로 작성합니다.

 

 

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

HTML에서 이벤트를 설정할 때는 큰따옴표 안에 실행할 코드를 넣었지만,

<button onclick="alert('executed')">

리액트에서는 함수 형태의 객체를 전달한다.

<button onClick = { (e) => console.log('hi') }> 안녕 </button>

앞서 버튼 예제에서도 화살표 함수 문법으로 함수를 만들어 전달했는데,

이렇게 바로 만들어서 전달해도 되고, 

렌더링 부분 외부에 미리 만들어서 전달해도 된다.

 

 

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

div, button, input, form, span 등의 DOM 요소에는 이벤트를 설정할 수 있지만,

우리가 직접 만든 컴포넌트에는 이벤트를 자체적으로 설정할 수 없다.

예를 들어 JeongSeo에 onClick 값을 설정한다면 JeongSeo를 클릭할 때 doSomething 함수를 실행하는 것이 아니라,

그냥 이름이 onClick인 props를 JeongSeo에게 전달해 줄 뿐입니다.

<JeongSeo onClick = { doSomething }/>

 

 

따라서 컴포넌트에 자체적으로 이벤트를 설정할 수는 없습니다.

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

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

 

 

 

 

=   (2)  이벤트 종류   =

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

●  Clipboard      

●  Composition 

●  Keyboard      

●  Focus            

●  Mouse           

●  Selection       

●  Touch            

●  UI                  

●  Wheel           

●  Media           

●  Image           

●  Animation     

●  Transition     

 

 

간단한 이벤트만 알아보긴 할 예정입니다!

 

 

 

 

 


-    2.  예제로 이벤트 핸들링 익히기    -

다음 단계로 실습하면서 이벤트 핸들링을 익혀봅시다!

 

 

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

(1 - 1)  컴포넌트 생성

src 디렉터리 내부에 EventPractice.js 파일을 만들고, 초기코드를 작성해 보겠습니다.

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

export default EventPractice;

 

 

(1 - 2)  App.js에서 EventPractice 렌더링

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

// App.js
import EventPractice from './EventPractice';

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

export default App;

 

 

 

 

=   (2)  onChange 이벤트 핸들링하기   =

(2 - 1)  onChange 이벤트 설정

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

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

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

여기 콘솔에 기록되는 e 객체SyntheticEvent로 웹 브라우저의 네이티브 이벤트를 감싸는 객체입니다.

네이티브 이벤트와 인터페이스가 같으므로 순수 자바스크립트에서 HTML 이벤트를 다툴때와 똑같이 사용하면 된다.

 

SyntheticEvent는 네이티브 이벤트와 달리 이벤트가 끝나고 나면 이벤트가 초기화되므로 참조할 수 없다.

 

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

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

 

 

 

(2 - 2)  state에 input 값 담기

생성자 메서드인 constructor에서 state 초깃값을 설정하고,

이벤트 핸들링 함수 내부에서 this.setState 메서드를 호출하여 state를 업데이트 해보자.

그다음 input의 value 값을 state에 있는 값으로 설정하자.

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

코드를 저장하고, 인풋창에 아무거나 입력해보자.

오류가 나지않고 제대로 입력할 수 있다면 state에 텍스트를 잘 담은 것이다.

 

 

 

(2 - 3)  버튼 누를 때 comment 값을 공백으로 설정

위에서 state에 우리가 입력한게 잘 들어갔는지, 제대로 반영된 건지 확인해 보자.

input 요소 코드 아래에 button 하나 만들어서, 

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

// EventPractice.js
import { 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
              })
            }
          }
        />
        <button onClick = {
          () => {
            alert(this.state.message);
            this.setState({
              message: ''
            });
          }
        }> 확인 </button>
      </div>
    );
  }
}

export default EventPractice;

이렇게 하면!

alert를 사용하여 현재 message 값을 화면에 표시하게 됩니다~

 

 

 

 

 

=   (3)  임의 메서드 만들기   =

앞에서

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

라고 배웠습니다.

 

그렇기에  이벤트를 처리할 때 렌더링을 하는 동시에 함수를 만들어서 전달해 주었습니다.

 

이 방법 대신 함수를 미리 준비하여 전달하는 방법도 있습니다.

성능상으로는 차이가 거의 없지만, 가독성이 훨씬 좋습니다.

 

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

 

 

 

(3 - 1)  기본 방식

// EventPractice.js
import { 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와 바인딩하는 작업이 필요합니다.

 

만약 바인딩하지 않는 경우라면 this가 undefined를 가리키게 된다.

 

현재 constructor 함수에서 함수를 바인딩하는 작업이 이뤄지고 있다.

 

 

 

(3 - 2)  Property Initializer Syntax를 사용한 메서드 작성

메서드 바인딩은 생성자 메서드에서 하는 것이 정석이다.

 

하지만 이 작업을 불편하다고 느낄 수 있는데, 새 메서드를 만들 때마다 constructor도 수정해야하기 때문이다.

이 작업을 좀 더 간단하게 하는 방법이 있다.

 

바로 바벨의 transform-class-properties 문법을 사용하여 화살표 함수 형태로 메서드를 정의하는 것입니다.

한번 봐보자!

// EventPractice.js
import { 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)  input 여러개 다루기   =

input이 여러 개일 때는 어떻게 작업을 해야할까요!

메서드를 여러 개 만들어야 할까요?

물론 그것도 하나의 방법이긴 하지만, 더 쉽게 처리하는 방법이 있다.

 

바로 event 객체를 활용하는 것입니다.

e.target.name 값을 사용하면 됩니다.

onChange 이벤트 핸들러에서 e.target.name은 해당 인풋의 name을 가리킵니다.

지금은 message인데, 이 값을 사용하여 state를 설정하면 쉽게 해결할 수 있다.

 

render 함수에서 name 값이 username인 input을 렌더링해 주었고,

state 쪽에도 username이라는 값을 추가해 주었습니다.

handleChange도 조금 변경했습니다.

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

 

 

 

여기 코드가 핵심입니다!

// EventPractice의 handleChange 함수

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

 

 

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

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

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

결과는 다음과 같다.

{
  'varianKey' : 'value'
}

 

 

 

 

 

=   (5)  onKeyPress 이벤트 핸들링   =

이벤에는 키를 눌렀을 때 발생하는 keyPress 이벤트를 처리해보자.

comment 인풋에서 Enter를 눌렀을 때 handleClick 메서드를 호출하도록 메서드를 작성해보자.

// EventPractice.js
import { 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 : ''
    });
  }
  
  
  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;

 

 

 

 


-    3.  함수 컴포넌트로 구현해 보기    -

방금 우리가 했던 작업을 함수 컴포넌트로도 똑같이 구현할 수 있습니다.

기존 EventPractice 컴포넌트를 모두 지우고 다음과 같이 작성해 보자.

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

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

 

인풋이 두 개밖에 없다면 이런 코드도 좋습니다.

하지만 인풋의 개수가 많아질 것 같으면 e.target.name을 활용하는게 좋을 수도 있습니다.

 

이번에는 userState를 통해 사용하는 상태에 문자열이 아닌 객체를 넣어보자.

// EventPractice.js
import { 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 객체를 사용해 주면 된다.

 

 

 

 

'리액트' 카테고리의 다른 글

컴포넌트의 라이프사이클 메서드  (0) 2023.02.28
컴포넌트 반복  (0) 2023.02.27
ref : DOM에 이름 달기  (1) 2023.02.23
컴포넌트  (0) 2023.02.18
리액트의 첫 시작 / JSX  (3) 2023.02.17
Comments