리액트를 다루는 기술 4장

riverkim·2022년 5월 31일
0
post-thumbnail

이 글은 책 리액트를 다루는 기술을 개인적으로 정리한 글 입니다.

이벤트 핸들링

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

리액트의 이벤트 시스템

import React, { 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>
      ()

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

이벤트 이름은 카멜 표기법으로 작성

onclick -> onClick

onkeyup -> onKeyUp

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

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

  <button onclick="alert('executed')">
    Click Me
  </button>

반면 ,리액트에서는 함수 형태의 객체를 전달

DOM 요소에만 이벤트를 설정

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

예를 들어 MyComponent에 onClick 값을 설정한다면 MyComponent를 클릭할 때 doSomething 함수를 실행하는 것이 아니라,
그냥 이름이 onClick인 props를 MyComponent에게 전달

<MyComponent onClick={doSomething}/>

따라서 컴포넌트에 자체적으로 이벤트를 설정할 수 X

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

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

이벤트 종류

리액트에서 지원하는 이벤트 종류

• Clipboard

• Composition

• Keyboard

• Focus

• Form

• Mouse

• Selection

• Touch

• UI

• Wheel

• Media

• Image

• Animation

• Transition

참고 (https://facebook.github.io/react/docs/events.html)

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

input 요소를 렌더링하는 코드와 해당 요소에 onChange 이벤트를 설정하는 코드

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;

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

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

export default EventPractice;

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;

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

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;

메서드 만들기

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

기존 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() {
    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 함수에서 함수를 바인딩하는 작업이 이루어지고 있음

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;

input 여러 개 다루기

input이 여러 개일 때 event 객체를 활용하면 state를 쉽게 다룰 수 있음

e.target.name 값을 사용하면 됨
onChange 이벤트 핸들러에서 e.target.name은 해당 인풋의 name을 가리킴 (username 혹은 message)
이 값을 사용하여 state를 설정

render 함수에서 name 값이 username인 input을 렌더링해 주었고, state 쪽에도 username이라는 값을 추가

import React, { Component } from "react";


class EventPractice extends Component {

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


// 객체 안에서 key를 [ ]로 감싸면 그 안에 넣은 레퍼런스가 가리키는 실제 값이 key 값으로 사용
// 예를 들어 다음과 같은 객체를 만들면
// const name = "variantKey";
// const object = {
//		[name]: "value"
// };
// 결과는 다음과 같습니다.
// 
// {
// "variantKey": "value"
// }


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;

onKeyPress 이벤트 핸들링

키를 눌렀을 때 발생하는 KeyPress 이벤트를 처리하는 방법
message 인풋에서 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;

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

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 객체를 사용

정리

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

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

함수형 컴포넌트에서 여러 개의 인풋 상태를 관리하기 위해 useState에서 form 객체를 사용하는 방법도 알아봤음

profile
Hello!

0개의 댓글