관리 메뉴

거니의 velog

(12) 이벤트 핸들링 2 본문

React_리액트 시작

(12) 이벤트 핸들링 2

Unlimited00 2023. 11. 30. 13:35

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

* 그럼 예제로 이벤트 핸들링을 익혀 보자. 앞으로 실습할 각 단계는 다음과 같다.


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

[1] 컴포넌트 생성

* 실습을 시작하기에 앞서 새 컴포넌트를 만들어 보자. src 디렉터리 내부에 EventPractice.js 파일을 만들자. 그리고 컴포넌트 초기 코드(rcc)를 작성하자.

import React, { Component } from 'react';

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

export default EventPractice;

[2] App.js에서 EventPractice 렌더링

* App 컴포넌트에서 EventPractice 를 불러와 렌더링하자.

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

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

export default App;

* 이제 웹 브라우저를 확인해 보면 다음과 같이 이벤트 연습이 렌더링된 화면을 볼 수 있다.


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

[1] onChange 이벤트 설정

* EventPractice 컴포넌트에 input 요소를 렌더링하는 코드와 해당 요소에 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;

* 코드를 저장하고, 웹 브라우저에서 크롬 개발자 도구[F12]를 열어 인풋에 아무것이나 입력해 보자.

* 이벤트 객체가 콘솔에 나타났다.

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

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

* 여기서 콘솔에 기록되는 e 객체는 SyntheticEvent로 웹 브라우저의 네이티브 이벤트를 감싸는 객체이다. 네이티브 이벤트와 인터페이스가 같으므로 순수 자바스크립트에서 HTML 이벤트를 다룰 때와 똑같이 사용하면 된다.

* SyntheticEvent는 네이티브 이벤트와 달리 이벤트가 끝나고 나면 이벤트가 초기화되므로 정보를 참조할 수 없다. 예를 들어, 0.5초 뒤에 e 객체를 참조하면 e 객체 내부의 모든 값이 비워지게 된다.

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

* 예를 들어 onChange 이벤트가 발생할 때, 앞으로 변할 인풋 값인 e.target.value를 콘솔에 기록해 보자.

* 코드를 다음과 같이 수정해 보자.

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);
            console.log(e.target.value);
          }}
        />
      </div>
    );
  }
}

export default EventPractice;

* 값이 바뀔 때마다 바뀌는 값을 콘솔에 기록하는 모습을 볼 수 있다.

[2] state에 input 값 담기

* 이번에는 이전에 배운 state에 input 값을 담아 보자.

* 이전에 배운 대로 생성자 메서드인 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;

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

[3] 버튼을 누를 때 comment 값을 공백으로 설정

* 정말로 우리가 입력한 값이 state에 잘 들어갔는지, 그리고 인풋에서 그 값을 제대로 반영하는지 한 번 검증해 보자. input 요소 코드 아래쪽에 button을 하나 만들고, 클릭 이벤트가 발생하면 현재 comment 값을 메시지 박스로 띄운 후 comment 값을 공백으로 설정해 보자.

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

export default EventPractice;

* alert을 사용하여 현재 message 값을 화면에 표시하게 했다.


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

* 이전에 주의 사항에서 "이벤트에 실행할 자바스크립트 코드를 전달하는 것이 아니라, 함수 형태의 값을 전달한다"라고 배웠다. 그렇기에 이벤트를 처리할 때 렌더링을 하는 동시에 함수를 만들어서 전달해 주었다. 이 방법 대신 함수를 미리 준비하여 전달하는 방법도 있다. 성능상으로는 차이가 거의 없지만, 가독성은 훨씬 높다. (하지만 상황에 따라 렌더링 메서드 내부에서 함수를 만드는 것이 더 편할 때도 있다. 이는 이후에 컴포넌트 매핑을 다룰 때 배운다.)

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

[1] 기본 방식

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 함수에서 함수를 바인딩하는 작업이 이루어지고 있다.

* 메서드 이름은 어떻게 정하나요?

- 메서드 이름은 여러분 마음대로 정해도 된다. 하지만 규칙을 정하는 것이 보기 편할 것이다.
  우리는 이벤트를 핸들링하는 메서드는 handle______ 형식으로 정하겠다.

[2] Property Initializer Syntax를 사용한 메서드 작성

* 메서드 바인딩은 생성자 메서드에서 하는 것이 정석이다. 하지만 이 작업을 불편하다고 느낄 수도 있다. 새 메서드를 만들 때마다 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;

* 훨씬 깔끔해 진 모양새이다.


(4) input 여러 개 다루기

* 자, 우리는 input 값을 state에 넣는 방법을 배웠다. 하지만 input이 여러 개일 때는 어떻게 작업할까? 메서드를 여러 개 만들어야 할까? 물론 그것도 하나의 해법이긴 하지만, 더 쉽게 처리하는 방법이 있다.

* 바로 event 객체를 활용하는 것이다. e.target.name 값을 사용하면 된다. onChange 이벤트 핸들러에서 e.target.name은 해당 인풋의 name을 가리킨다. 지금은 message이다. 이 값을 사용하여  state를 설정하면 쉽게 해결할 수 있다. 코드를 한번 살펴보자.

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

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 두 개

* 여기서 다음 코드가 핵심이다.

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

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

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

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

결과는 다음과 같다.

{
	'variantKey' : 'value'
}

(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,
    });
  };

  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;

* 두 번째 텍스트 인풋에서 텍스트를 입력하고 [Enter]를 눌러 보자. handleClick 메서드가 실행되는 모습을 볼 수 있다.


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

* 방금 우리가 했던 작업을 함수형 컴포넌트로도 똑같이 구현할 수 있다. 기존 EventPractice 컴포넌트를 모두 지우고 다음과 같이 작성해 보자.

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;

* 기능이 이전과 같이 잘 작동한다.

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

* 인풋이 두 개밖에 없다면 이런 코드도 나쁘지는 않다. 하지만 인풋의 개수가 많아질 것 같으면 e.target.name을 활용하는 것이 더 나을 수 있다.

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

* 코드를 다음과 같이 수정해 보자.

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 객체를 사용해 주면 된다.


4. 정리

* 리액트에서 이벤트를 다루는 것은 순수 자바스크립트 또는 jQuery를 사용한 웹 애플리케이션에서 이벤트를 다루는 것과 비슷하다. 리액트의 장점 중 하나는 자바스크립트에 익숙하다면 쉽게 활용할 수 있다는 것이다. 따라서 기존 HTML DOM Event를 알고 있다면 리액트의 컴포넌트 이벤트도 쉽게 다룰 수 있을 것이다.

* 이 장에서는 클래스형 컴포넌트도 구현해 보고 함수형 컴포넌트로도 구현해 보았다. 클래스형 컴포넌트로 할 수 있는 대부분의 작업은 함수형 컴포넌트로도 구현할 수 있다.

* 또한, 함수형 컴포넌트에서 여러 개의 인풋 상태를 관리하기 위해 useState에서 form 객체를 사용하는 방법도 배워 보았다. 우리가 이후에 배울 useReducer와 커스텀 Hooks를 사용하면 이 작업을 훨씬 더 편하게 할 수도 있다.

'React_리액트 시작' 카테고리의 다른 글

(14) ref:DOM 에 이름 달기 2  (0) 2023.11.30
(13) ref:DOM 에 이름 달기 1  (0) 2023.11.30
(11) 이벤트 핸들링 1  (2) 2023.11.30
(10) 컴포넌트 3  (0) 2023.11.29
(9) 컴포넌트 2  (0) 2023.11.29