All Articles

리액트에서 이벤트 처리하기

들어가기 전에

  • 이벤트 리스너: 이벤트를 처리하는 함수
  • 이벤트 핸들러 속성명은 카멜케이스로만 작성 (HTML에서는 onclick, 리액트는 onClick)
  • 이벤트 핸들러에는 필히 함수를 지정 (HTML에서는 문자열로 코드를 지정)

    // HTML과 리액트에서 이벤트 리스너를 다룰 때 다른 점
    <div onclick="console.log('Hello');"></div>
    <div onClick={console.log('Hello')}></div>
  • DOM 요소에만 이벤트 지원

    • 커스텀 리액트 컴포넌트에서는 HTML 이벤트를 지원하지 않음
    • 하지만 내부 Element에 DOM 요소를 담아 핸들러를 지정할 수 있음. 아래 예시 참고
// App.js

import React from 'react';
import PropTypes from "prop-types";
import Counter from "Counter";
import 'App.css';

class App extends React.Component {
    render() {
        return (
            <Counter onClick={ () => console.log("Clicked")} />
        );
    }
}

export default App;
// Counter.js

import React from "react";
import PropTypes from "prop-types";

class Counter extends React.Component {
    static propTypes = {
        onClick: PropTypes.func,
    }

    render() {
        return (
            // 내부 element에 DOM 요소를 담아 핸들러 지정 가능
            <div onClick={this.props.onClick}>
                Counter
            </div>
        )
    }
}

export default Counter;

이벤트 핸들러와 bind

아래 세 가지 케이스에서

  • handleChangeInput1 함수의 this는 e.target을 가리킨다.
  • handleChangeInput2 함수의 this는 현재 컴포넌트를 가리킨다.
  • handleChangeInput3 함수의 this도 현재 컴포넌트를 가리킨다.
class App extends React.Component {
    constructor(props) {
        super(props);
        this.handleChangeInput2 = this.handleChangeInput2.bind(this);
    }

    handleChangeInput1 = function(e) {
        const { name, value } = e.target;
        console.log(`[handleChangeInput1] name=${name}, value=${value} <= this=`, this);
    }

    handleChangeInput2 = function(e) {
        const { name, value } = e.target;
        console.log(`[handleChangeInput2] name=${name}, value=${value} <= this=`, this);
    }

    handleChangeInput3 = (e) => {
        const { name, value } = e.target;
        console.log(`[handleChangeInput2] name=${name}, value=${value} <= this=`, this);
    }

    render () {
        return (
            <input name={"myquery"} onChange={this.handleChangeInput2} />
        )
    }
}

왜 그럴까?

function을 사용하면서 this가 해당 함수를 가리키도록 바뀌는데, 다시 현재 컴포넌트를 가리키도록 하려면 bind(this)로 바꿔줘야 한다.

매번 bind로 바꿔주기 귀찮으니 화살표 함수를 사용하는 것이 편하다. ECMA 표준은 아니라 babel 플러그인이 필요하지만, create-react-app으로 리액트 프로젝트를 생성하면 다른 설정 없이 바로 사용할 수 있다.

정리하자면,

  • handleChangeInput1 함수는 bind 작업을 해주지 않았기 때문에 this는 e.target을 가리킨다.
  • handleChangeInput2 함수의 bind 작업을 해주었으므로 this는 현재 컴포넌트를 가리킨다.
  • handleChangeInput3이 화살표 함수를 사용했기 때문에 bind 작업 없어도 this가 현재 컴포넌트를 가리킨다.

화살표 함수 사용 예시

// App.js

import React from 'react';
import PropTypes from "prop-types";
import Counter from "Counter";
import 'App.css';

class App extends React.Component {
    onChange = (e) => {
        const { value } = e.target;
        console.log('changed value: ', value, this);
    }  // 화살표 함수 사용 예시 (this가 컴포넌트를 가리킴)

    render() {
        return (
            <div>
                <Counter onClick={ () => console.log("Clicked")} />
                <input onChange={this.onChange} />
            </div>
            
            /*
            리액트 element는 항상 컴포넌트 1개만 반환해야 하기 때문에
            div 같은 것으로 묶어주지 않으면 에러남
            div로 묶기 싫으면 아래처럼 <React.Fragment>로 묶어도 됨
            혹은 <></> 빈 태그로 묶어도 됨

            <React.Fragment>
                <Counter onClick={ () => console.log("Clicked")} />
                <input />
            </React.Fragment>
            */
        );
    }
}

export default App;

input 입력 문자열을 상태값으로 저장하기

// App.js

import React from 'react';
import PropTypes from "prop-types";
import Counter from "Counter";
import 'App.css';

class App extends React.Component {
    state = {
        myquery: "",
    }

    onChange = (e) => {
        const { name, value } = e.target;
        this.setState({
            [name]: value,  // name 변수에 담긴 문자열이 key 이름이 됨
        })
    }

    render() {
        return (
            <div>
                <input name="myquery" onChange={this.onChange} />
            </div>
        );
    }
}

export default App;

input 입력 문자열을 상태값으로 저장하기 예시

이렇게 보인다

이벤트 리스너 재활용

같은 onChange 이벤트 리스터를 다른 요소에서도 사용할 수 있다.

import React from 'react';
import PropTypes from "prop-types";
import Counter from "Counter";
import 'App.css';

class App extends React.Component {
    state = {
        myquery: "",
        language: "",
    }

    // 만들어준 이벤트 리스너를 범용적으로 사용 가능
    onChange = (e) => {
        const { name, value } = e.target;
        this.setState({
            [name]: value,
        })
    }

    render() {
        return (
            <div>
                <input name="myquery" onChange={this.onChange} />
                <input name="language" onChange={this.onChange} />
            </div>
        );
    }
}

export default App;

번외) console.log말고 다른 방법으로 디버깅 하기

// App.js

import React from 'react';
import PropTypes from "prop-types";
import Counter from "Counter";
import 'App.css';

class App extends React.Component {
    state = {
        myquery: "",
        language: "",
    }

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

    render() {
        return (
            <div>
                <input name="myquery" onChange={this.onChange} />
                <input name="language" onChange={this.onChange} />
                <hr />
                {JSON.stringify(this.state)}
            </div>  // console.log말고도 stringify로 상태값 직렬화해서 디버깅용으로 확인할 수 있음
        );
    }
}

export default App;

JSON 직렬화해 객체를 실시간으로 확인하면서 디버깅할 수 있다.

JSON 직렬화를 통한 디버깅 예시