쌩로그

리액트를 다루는 기술 - 04. 이벤트 핸들링 본문

Front/리액트를 다루는 기술

리액트를 다루는 기술 - 04. 이벤트 핸들링

.쌩수. 2024. 4. 8. 12:20
반응형

목록

  1. 포스팅 개요
  2. 본론
      2-1. 리액트의 이벤트 시스템
      2-2. 예제로 이벤트 핸들링 익히기
      2-3.함수형 컴포넌트로 구현해보기
  3. 요약

1. 포스팅 개요

해당 포스팅은 리액트를 다루는 기술04장 이벤트 핸들링 을 학습하며 정리한 포스팅이다.

2. 본론

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

  • 버튼에 마우스 커서를 올렸을 때는 onmouseover
  • 마우스를 클릭했을 때는 onclick
  • Form 요소의 값이 바뀔 때 onchange

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

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

사용법은 HTML에서 이벤트를 작성하는 것과 비슷한데, 주의해야 할 몇 가지 사항이 있다.

이벤트 사용시 주의 사항

  • 이벤트 이름은 카멜 표기법으로 작성한다.
    • HTML의 onclick을 리액트에서는 onClick으로 작성해야 한다.
    • HTML의 onkeyup을 리액트에서는 onKeyUp으로 작성해야 한다.
  • 이벤트에 실행할 자바스크립트 코드를 전달하는 것이 아니라, 함수 형태의 값을 전달한다.
    • 리액트에서는 함수 형태의 객체를 전달한다.
    • 화살표 함수 분법으로 함수를 만들어 전달해도 되고, 렌더링 부분 외부에 미리 만들어서 전달할 수 있다.
  • DOM 요소에만 이벤트를 설정할 수 있다.
    • 즉 div, button, input, form, span 등의 DOM 요소에는 이벤트를 설정할 수 있지만, 직접 만든 컴포넌트에는 이벤트를 자체적으로 설정할 수 없다.
    • 컴포넌트에 자체적으로 이벤트를 설정할 수는 없지만, 전달받은 props를 컴포넌트 내부의 DOM 이벤트로 설정할 수는 있다.

이벤트 종류

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

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

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

컴포넌트 생성 및 불러오기

컴포넌트 생성

예제애 쓸 새 컴포넌트를 만든다.

import React, { Component } from "react";  

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

다음과 같이 나옴을 확인할 수 있다.

onChange 이벤트 설정을 해보자.

방금 작성했던 EventPractice 컴포넌트에 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;

다음과 같이 확인할 수 있다.

크롬 개발자 도구를 열고 input에 아무것이나 입력해보자.

위와 같은 Event 객체가 나온다.

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

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

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

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

다음과 같이 코드를 수정한다.

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

'테스트'를 입력했는데, 값이 바뀌는 족족 콘솔에 출력되고 있다.

state에 input 값 담기

이제 state에 input 값을 담아보자.
생성자 메서드인 constructor에서 state 초깃값을 설정하고, 이벤트 핸들링 함수 내부에서 this.setState 메서드를 호출하여 state를 업데이트 해보자.

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

import React, { Component } from "react";  

class EventPracticeInputValueAtState 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 EventPracticeInputValueAtState;

(컴포넌트 명은 예제마다 다르게 하려고 임의로 설정했다.)

input에 아무거나 입력했는데, 오류가 발생하지 않으면 state에 텍스트틀 잘 담은 것이다.

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

입력 값이 state에 잘 들어갔는지, 인풋에서 그 값을 제대로 반영하는지 검증해보자.

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

import React, { Component } from "react";  

class EventPracticeCommentBlank 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 EventPracticeCommentBlank;

여기서 확인을 누르면 아래와 같이 공백이 된다.

#### 임의 메서드 만들기

이벤트에 실행할 자바스크립트 코드를 전달하는 것이 아니라, 함수 형태의 값을 전달한다라고 했었다. 그래서 이벤트 처리시 렌더링을 하는 동시에 함수를 만들어서 전달했다.

이 방법 대신 함수를 미리 준비하여 전달하는 방법도 있다.
성능상으로는 차이가 거의 없지만, 가독성은 훨씬 높다.(상황에 따라 렌더링 메서드 내부에서 함수를 만드는 것이 더 편할 때도 있다.)

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

기본 방식

import React, { Component } from "react";  

class EventPracticeBasic 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 EventPracticeBasic;

기능은 이전과 동일하다.

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

메서드 바인딩은 생성자 메서드에서 하는 것이 정석이다.
하지만 이 작업이 불편하다고 느낄 수 있다.
새 메서드를 만들 때마다 constructor도 수정해야하기 때문이다.

해당 작업을 더 간단히 할 수 있는데, 바벨의 transform-class-properties 문법을 사용하여 화살표 함수 형태로 메서드를 정의하는 것이다.

import React, { Component } from "react";  

class EventPracticeArrow 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 EventPracticeArrow;

contructor 를 없애면서 더 간결해졌다.

input 여러 개 다루기

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 EventPracticeMultiInput extends Component {  

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

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

    handleClick = () => {  
        alert(this.stte.username + ': ' + this.state.message);  
        this.setState({  
            username: '',  
            message: ''  
        });  
    }  

    render() {  
        return (  
            <div>  
                <h1>이벤트 연습</h1>  
                <input                    type="text"  
                    name="username"  
                    placeholder="사용자명"  
                    value={this.state.message}  
                    onChange={this.handleChange}  
                />  
                <input                    type="text"  
                    name="message"  
                    placeholder="아무거나 입력해 보세요"  
                    value={this.state.message}  
                    onChange={this.handleChange}  
                />  
                <button onClick={this.handleClick}>확인</button>  
            </div>        );  
    }  
}  

export default EventPracticeMultiInput;

위와 같이 동작한다.

핵심 코드는 다음과 같다.

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

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

다음과 같은 객체를 만들면

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

결과는 다음과 같다.

{
    'variantKey' : 'value'
}

onKeyPress 이벤트 핸들링

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

import React, { Component } from "react";  

class EventPracticeOnKeyPress 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: ''  
        });  
    }  

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

export default EventPracticeOnKeyPress;

참고로 예제에 있는 onKeyPress가 현재 Deprecated 되었으므로 onKeyDown을 주었다.
onKeyUp도 있지만, onKeyDown이 현재는 더 적합할 거 같다고 생각했다.

onKeyUp과 onKeyDonw의 차이는 다음 블로그에 잘 나와있다.

https://velog.io/@diso592/onKeyPress-is-deprecated

Enter를 누르면 handleClick 메서드가 실행된다.

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

방금까지 했던 작업을 함수형 컴포넌트로도 똑같이 구현할 수 있다.

import React, {Component, useState} from "react";  

const EventHandlingByFunction = () => {  

    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 onKeyDown = 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}  
                onKeyDown={onKeyDown}  
            />  
            <button onClick={onClick}>확인</button>  
        </div>    );  
}  


export default EventHandlingByFunction;

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

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

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

import React, {Component, useState} from "react";  

const EventHandlingByObject = () => {  

    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 onKeyDown = 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}  
                onKeyDown={onKeyDown}  
            />  
            <button onClick={onClick}>확인</button>  
        </div>    );  
}  


export default EventHandlingByObject;

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

3. 요약

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

함수형 컴포넌트에서 여러 개의 인풋 상태를 관리하기 위해 useState에서 form 객체를 사용해보았다.
8장에서 배우는 userReducer와 커스텀 Hooks를 사용하면 이 작업을 훤씬 더 편하게 할 수 있다.

728x90
Comments