프론트엔드/리액트를 다루는 기술

리액트를 다루는 기술, 이벤트 핸들링

Hyeon_E 2023. 6. 17. 01:26

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

 

[ 4.1 리액트의 이벤트 시스템 ]

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

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

  • 이벤트 이름은 카멜 표기법으로 작성
    • HTML: onclick → React: onClick
  • 이벤트에 실행할 JS 코드를 전달하는 것이 아닌, 함수 형태의 값을 전달
    • 화살표 함수 문법으로 함수를 만들어 전달
    • 렌더링 부분 외부에 미리 만들어서 전달
  • DOM 요소에만 이벤트를 설정할 수 있음
    • DOM 요소에는 이벤트를 설정할수 있지만 컴포넌트에는 이벤트를 자체적으로 설정할 수 없음

▶ 4.1.2 이벤트 종류

리액트에서 지원하는 이벤트의 종류중 몇개

Clipboard Composition Keyboard Focus Form Mouse Selection
Touch UI Wheel Media Image Animation Transition

 

[ 4.2 예제로 핸들링 익히기 ]

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

4.2.1.1 컴포넌트 생성

 

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 렌더링

 

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

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

 

▶ 4.2.2 onChange 이벤트 핸들링하기

4.2.2.1 onChange 이벤트 설정

 

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

 

 

여기서

 

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

 

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

네이티브 이벤트와 인터페이스가 같으므로 순수 JS에서 HTML 이벤트를 다룰때와 똑같이 사용하면 됨

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

예를 들어 0.5초뒤에 e객체를 참조하면 e객체 내부의 모든 값이 비워지게 됨

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

 

4.2.2.2 state에 input 값 담기

 

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,
            });
          }}
        ></input>
      </div>
    );
  }

 

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

 

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

 

▶ 4.2.3 임의 메서드 만들기

이벤트의 실행할 자바스크립트 코드를 전달하는 것이 아닌, 함수형태의 값을 전달해야되기 때문에

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

이 방법 대신 함수를 미리 준비하여 전달하는 방법도 있음. 성능상으로는 차이가 거의 없지만 가독성을 훨씬 높음

 

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

 

함수가 호출될 때 this는 호출부에 따라 결정되므로, 클래스의 임의 메서드가 특정 HTML 요소의 이벤트로 등록되는 과정에서 메서드와 this의 관계가 끊어져 버림

임의 메서드가 이벤트로 등록되어도 this를 컴포넌트 자신으로 제대로 가리키기 위해서는 메서드를 this와 바인딩하는 작업이 필요함. 바인딩하지 않는 경우, this가 undefined를 가리키게 됨

 

더보기

메서드 이름을 마음대로 정해도 됨. 하지만 규칙을 정하면 보기가 편함

이 책에서는 메서드를 handle_형식으로 정했음. 리액트에서는 이벤트 처리를 핸들링이라고 함

 

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

메서드 바인딩은 생성자 메서드에서 하는것이 작성이나 이 작업을 좀 더 간단하게 하는 방법이 있음

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

 

state = { message: "" };

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

 

▶ 4.2.4 input 여러 개 다루기

input이 여러개일때는 메서드를 여러개 만드는 방법도 있지만 event 객체를 활용하면 더 쉽게 처리할 수 있음

e.target.name값을 사용하면 됨 onChange 이벤트 핸들러에서 e.target.name은 인풋의 name을 가리킴

 

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

 

여기서 이 코드가 핵심임

  handleChange = (e) => {
    this.setState({
      [e.target.name]: e.target.value,
      // 여기서는 username, message가 들어감
    });
  };

 

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

 

// 예시
const name = 'variantKey'
const object = {
  [name]: 'value'
}

// 결과

 

▶ 4.2.5 onKeyPress 이벤트 핸들링

키를 눌렀을때 발생하는 KeyPress 이벤트를 처리하는 방법

comment 인풋에서 Enter를 눌렀을때 handleClick 메서드를 호출

 

최종코드

import React, { Component } from "react";

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

  handleChange = (e) => {
    this.setState({
      [e.target.name]: e.target.value,
      // 여기서는 username, message가 들어감
    });
  };

  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>
        <input
          type="text"
          name="message"
          placeholder="아무거나 입력해 보세요"
          value={this.state.message}
          onChange={this.handleChange}
          onKeyPress={this.handleKeyPress}
        ></input>
        <button onClick={this.handleClick}>확인</button>
      </div>
    );
  }
}

export default EventPractice;

 

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

함수형 컴포넌트로도 똑같이 구현

 

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>
      <input
        type="text"
        name="message"
        placeholder="아무거나 입력해 보세요"
        value={message}
        onChange={onChangeMessage}
        onKeyPress={onKeyPress}
      ></input>
      <button onClick={onClick}>확인</button>
    </div>
  );
};

 

input이 2개라서 onChange 관련 함수 두개를 만들었지만 인풋이 개수가 많아진다면 더 많은 함수가 필요하기에 개수가 많을수록 e.target.name을 활용하는 것이 더 좋을 수 있음

 

useState를 통해 사용하는 상태에 문자열이 아닌 객체를 넣는 방식

 

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,
    };
  };
  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>
      <input
        type="text"
        name="message"
        placeholder="아무거나 입력해 보세요"
        value={message}
        onChange={onChange}
        onKeyPress={onKeyPress}
      ></input>
      <button onClick={onClick}>확인</button>
    </div>
  );
};

 

e.target.name 값을 활용하려면 useState를 쓸 때 인풋 값들이 들어 있는 form 객체를 사용해 주면 됨

 

[ 4.4 정리 ] 

리액트에서 이벤트를 다루는 것은 순수 JS 또는 jQuery를 사용한 웹 애플리케이션에서 이벤트를 다루는 것과 비슷함

리액트의 장점 중 하나는 JS에 익숙하다면 쉽게 활용할 수 있다는 점

기존 HTML DOM Event를 알고 있다면 리액트의 컴포넌트 이벤트도 쉽게 다룰 수 있음

크래스형 컴포넌트로 할 수 있는 대부분의 작업은 함수형 컴포넌트로도 구현할 수 있음