게시글

5만명이 선택한 평균 별점 4.9의 제로초 프로그래밍 강좌! 로드맵만 따라오면 됩니다! 클릭
강좌4 - React - 8년 전 등록 / 4년 전 수정

컴포넌트 이벤트 연결(바인딩)

안녕하세요. 이번 시간에는 React 컴포넌트에 이벤트를 연결(바인딩)하는 방법을 알아보겠습니다. hook을 배우려고 하시는 분들은 이 강좌 대신 useCallback 강좌 로 넘어가세요. 물론 저는 class 컴포넌트 문법도 알아두시는 것을 권장드립니다.

import React, { Component } from 'react';
import PropTypes from 'prop-types';

class Basic extends Component {
  constructor(props) {
    super(props);
    this.state = {
      hidden: false,
    };
    this.onClickButton = this.onClickButton.bind(this);
  }

  onClickButton() {
    this.setState(() => ({ hidden: true }));
  }

  render() {
    return (
      <div>
        <span>저는 {this.props.lang} 전문 {this.props.name}입니다!</span>
        {!this.state.hidden && <span>{this.props.birth}년에 태어났습니다.</span>}
        <button onClick={this.onClickButton}>숨기기</button>
      </div>
    );
  }
}

Basic.propTypes = {
  name: PropTypes.string.isRequired,
  birth: PropTypes.number.isRequired,
  lang: PropTypes.string,
};

Basic.defaultProps = {
  lang: 'Javascript',
};

export default Basic;

지난 시간에 봤던 컴포넌트죠? 그런데 한 가지 달라진 부분이 있습니다. button의 onClick 속성을 따로 onClickButton 메소드로 분리했는데요. 그게 전부가 아닙니다. ES2015 class에서만 있는 특별한 현상이 있습니다.

바로 constructor 안에 this.onClickButton = this.onClickButton.bind(this); 라는 이상한 코드가 들어있습니다. 이 부분이 이벤트를 바인드(bind)하는 부분입니다. 저는 처음에 도대체 this.onClickButton에 this를 왜 bind하는 건지 이해가 되지 않았습니다. 그런데 함수의 원리를 알고 보면 이해가 됩니다.

onClickButton() {
  this.setState(() => ({ hidden: true }));
}

이 부분은 ES5 코드로 보면

onClickButton: function() {
  this.setState(() => ({ hidden: true }));
}

인데요. 문제점은 this.onClickButton = this.onClickButton.bind(this); 를 하지 않으면 render 메소드에서 this.onClickButton 함수를 호출했을 때 함수의 this가 windowundefined가 되어버립니다. 따라서 window나 undefined에는 setState 메소드가 없기 때문에 this.setState 호출 시 에러가 발생하죠.

왜 this가 window나 undefined가 되는지 알기 위해서는, this.onClickButton이 실행되는 환경을 잘 보아야 합니다. this.onClickButton이 실행되는 순간 (function() { this.setState(() => ({ hidden: true })); })(); 가 실행되는데, 이때의 this는 window입니다. 이 함수 자체만 놓고 보면, 자바스크립트 엔진은 this가 뭔지 모르기 때문에 그냥 최상위와 연결시켜버립니다.

이해가 잘 안 되나요? 그렇다면 이런 복잡한 생각 없이 쉽게 하는 방법이 있습니다. ES2015의 새로운 기능인 arrow function을 사용하면 됩니다. arrow function은 this를 자동으로 bind해주기 때문에 간단합니다. 대신 @babel/plugin-proposal-class-properties 가 필요합니다. 위의 컴포넌트는 다음과 같아집니다.

import React, { Component } from 'react';
import PropTypes from 'prop-types';

export default class Basic extends Component {
  static propTypes = {
    name: PropTypes.string.isRequired,
    birth: PropTypes.number.isRequired,
    lang: PropTypes.string,
  };

  static defaultProps = {
    lang: 'Javascript',
  };

  state = {
    hidden: false,
  };

  onClickButton = () => {
    this.setState(() => ({ hidden: true }));
  }

  render() {
    return (
      <div>
        <span>저는 {this.props.lang} 전문 {this.props.name}입니다!</span>
        {!this.state.hidden && <span>{this.props.birth}년에 태어났습니다.</span>}
        <button onClick={this.onClickButton}>숨기기</button>
      </div>
    );
  }
}

static 키워드 덕분에 Basic 컴포넌트와 따로 분리되어 있던 defaultProps와 propTypes가 안으로 들어갔습니다. 또 constructor 안에 있던 state는 바깥으로 나왔고요. onClickButton 메소드까지 arrow function으로 만들면 이제 constructor가 필요없어집니다. 복잡한게 싫다 싶으면 이 방법을 사용합니다. 앞으로도 이 방법을 사용하겠습니다.

다음 시간에는 컨텍스트(context) 참조(Reference)에 대해 알아보겠습니다!

조회수:
0
목록
투표로 게시글에 관해 피드백을 해주시면 게시글 수정 시 반영됩니다. 오류가 있다면 어떤 부분에 오류가 있는지도 알려주세요! 잘못된 정보가 퍼져나가지 않도록 도와주세요.
Copyright 2016- . 무단 전재 및 재배포 금지. 출처 표기 시 인용 가능.
5만명이 선택한 평균 별점 4.9의 제로초 프로그래밍 강좌! 로드맵만 따라오면 됩니다! 클릭

댓글

3개의 댓글이 있습니다.
4년 전
this가 window에 바인딩 된다는 것까지는 이해를 했습니다. 하지만 생성자에 this.onClickButton = this.onClickButton.bind(this);를 하는 이유는 여전히 잘 모르겠습니다... 이렇게 한다고해서 실행시에 this가 해당 클래스에 바인딩되는 것은 아니지 않나요?
4년 전
클래스에 바인딩하는 것이 아니라 onClickButton 함수에 this(인스턴스)를 바인딩하는 것입니다.
6년 전
Class로 Basic 컴포넌트를 만드는거라면 클래스 안쪽에서 선언한 함수(onClickButton)은 메서드로 생성되나요? 메서드는 화살표함수로 선언하면 메서드안의 this가 window를 가르키지 않나요? 화살표함수로 선언하면 binding을 따로 할필요가 없다는 부분이 이해가 가지않네요
6년 전
혹시나 일반함수로 선언한 render() 안에서 선언되는 메서드이기 때문에 가능한건가요?
6년 전
함수선언문과 화살표 함수 내에서 각각 console.log(this)를 해보시면 이해가 빠르실 듯 합니다. 화살표함수는 외부 스코프의 this를 자동으로 바인딩하기 때문에 바인딩을 따로 할 필요가 없습니다. 화살표 함수가 아니라 함수선언문일 때 오히려 this는 window입니다. 화살표 함수일 때는 상황마다 다 다릅니다.
7년 전
"되늕" 오타 있네요.