8-1강. Handling Events

정원·2023년 1월 31일
0

React

목록 보기
24/42

2023.01.31 Event의 정의 및 Event 다루기
React 엘리먼트에서 이벤트를 처리하는 방식은 DOM 엘리먼트에서 이벤트를 처리하는 방식과 매우 유사합니다.

DOM event

아래 코드는 버튼이 눌리면 activateLasers( ) 함수를 호출하도록 되어있습니다.
DOM에서는 클릭 이벤트를 처리할 함수를 onclick을 통해서 전달합니다.

<button onclick="activateLasers()">
  Activate Lasers
</button>

React event

아래 코드는 DOM event와 동일한 기능을 하도록 만든 리액트 코드입니다.

<button onClick={activateLasers}>
  Activate Lasers
</button>

DOM event VS React event

  1. onclick VS onClick
  • React의 이벤트는 소문자 대신 캐멀 케이스(camelCase)를 사용합니다.
  1. 문자열 VS 함수
  • onclick="activateLasers()"
  • onClick={activateLasers}
    - JSX를 사용하여 문자열이 아닌 함수로 이벤트 핸들러를 전달합니다.
  1. preventDefault
    또 다른 차이점으로,
    React에서는 false를 반환해도 기본 동작을 방지할 수 없습니다.
    반드시 preventDefault를 명시적으로 호출해야 합니다.

예를 들어, 일반 HTML에서 폼을 제출할 때 가지고 있는 기본 동작을 방지하기 위해 다음과 같은 코드를 작성할 수 있습니다.

<form onsubmit="console.log('You clicked submit.'); return false">
  <button type="submit">Submit</button>
</form>

React에서는 다음과 같이 작성할 수 있습니다.

function Form() {
  function handleSubmit(e) {
    e.preventDefault();
    console.log('You clicked submit.');
  }

  return (
    <form onSubmit={handleSubmit}>
      <button type="submit">Submit</button>
    </form>
  );
}

여기서 e는 합성 이벤트입니다.

React는 W3C 명세에 따라 합성 이벤트를 정의하기 때문에 브라우저 호환성에 대해 걱정할 필요가 없습니다.
React 이벤트는 브라우저 고유 이벤트와 정확히 동일하게 동작하지는 않습니다.
더 자세한 사항은 합성 이벤트을 참고하시기 바랍니다.

이벤트 핸들러(Event Handler)

  • 이벤트 핸들러 : 이벤트가 발생했을 때 해당 이벤트를 처리하는 함수
    또한 이벤트가 발생하는 것을 계속 듣고 있다는 의미로 이벤트 리스너(Event Listener)라고 부르기도 한다.

React를 사용할 때 DOM 엘리먼트가 생성된 후 리스너를 추가하기 위해 addEventListener를 호출할 필요가 없습니다.
대신, 엘리먼트가 처음 렌더링될 때 리스너를 제공하면 됩니다.


그렇다면 이벤트 핸들러를 어떻게 추가해야 할까요?
아래 예제 코드를 봅시다.

ES6 클래스를 사용하여 컴포넌트를 정의할 때,
일반적인 패턴은 이벤트 핸들러를 클래스의 메서드로 만드는 것입니다.
예를 들어, 다음 Toggle 컴포넌트는 사용자가 “ON”과 “OFF” 상태를 토글 할 수 있는 버튼을 렌더링합니다.

class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};

    // 콜백에서 `this`가 작동하려면 아래와 같이 바인딩 해주어야 합니다.
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState(prevState => ({
      isToggleOn: !prevState.isToggleOn
    }));
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}

위 코드에는 Toggle이라는 클래스 컴포넌트가 나옵니다.
그리고 컴포넌트의 state에는 isToggleOn이라는 Boolean 변수가 하나 있습니다.
버튼을 클릭하면 이벤트 핸들러 함수인 handleClick() 함수를 호출하도록 되어 있는데,
여기서 눈여겨봐야 할 곳은 바로 handleClick() 함수를 정의하는 부분과
this. handleClick = this.handleClick.bind (this); 부분입니다.

 handleClick() {
    this.setState(prevState => ({
      isToggleOn: !prevState.isToggleOn
    }));
  }

먼저 handleClick () 함수의 정의 부분은 일반적인 함수를 정의하는 것과 동일하게 괄호와 중괄호를 사용해서 클래스의 함수로 정의하고 있습니다.

this.handleClick = this.handleClick.bind(this);

이렇게 정의된 함수를 constructor()에서 bind ()를 이용하여 this. handleClick에 대입해 줍니다.

JSX 콜백 안에서 this의 의미에 대해 주의해야 합니다.
JavaScript에서 클래스 메서드는 기본적으로 바인딩되어 있지 않기 때문입니다.
this.handleClick을 바인딩하지 않고 onClick에 전달하였다면, 함수가 실제 호출될 때 this는 undefined가 됩니다.

이는 React만의 특수한 동작이 아니며, JavaScript에서 함수가 작동하는 방식의 일부입니다.
일반적으로 onClick={this.handleClick}과 같이 뒤에 ( )를 사용하지 않고 메서드를 참조할 경우, 해당 메서드를 바인딩 해야 합니다.

bind를 호출하는 것이 불편하다면, 이를 해결할 수 있는 두 가지 방법이 있습니다.
콜백을 올바르게 바인딩하기 위해 퍼블릭 클래스 필드 문법을 활용할 수 있다.

퍼블릭 클래스 필드 문법

class LoggingButton extends React.Component {
  // 이 문법은 `this`가 handleClick 내에서 바인딩되도록 합니다.
  // 주의: 이 문법은 *실험적인* 문법입니다.
  handleClick = () => {
    console.log('this is:', this);
  };

  render() {
    return (
      <button onClick={this.handleClick}>
        Click me
      </button>
    );
  }
}

Create React App에서는 이 문법이 기본적으로 설정되어 있습니다.

화살표 함수

클래스 필드 문법을 사용하고 있지 않다면, 콜백에 화살표 함수를 사용하는 방법도 있습니다.

class LoggingButton extends React.Component {
  handleClick() {
    console.log('this is:', this);
  }

  render() {
    // 이 문법은 `this`가 handleClick 내에서 바인딩되도록 합니다.
    return (
      <button onClick={() => this.handleClick()}>
        Click me
      </button>
    );
  }
}

이 문법의 문제점은 LoggingButton이 렌더링될 때마다 다른 콜백이 생성된다는 것입니다.
대부분의 경우 문제가 되지 않으나,
콜백이 하위 컴포넌트에 props로서 전달된다면 그 컴포넌트들은 추가로 다시 렌더링을 수행할 수도 있습니다.
이러한 종류의 성능 문제를 피하고자, 생성자 안에서 바인딩하거나 클래스 필드 문법을 사용하는 것을 권장합니다.

다만 지금은 클래스 컴포넌트를 거의 사용하지 않기 때문에
위 내용은 참고로만 알고 있기 바랍니다.


위에 나왔던 Toggle 컴포넌트를 함수 컴포넌트로 변환하면 아래와 같습니다.

function Toggle(props) {
    const [isToggleOn, setIsToggleOn] = useState(true);

    //방법1. 함수 안에 함수로 정의
    function handleClikck() {
        setIsToggleOn((isToggleOn) => !isToggleOn);
    }

    //방법2. arrow function을 사용하여 정의
    const handleClikck = () => {
        setIsToggleOn((isToggleOn => !isToggleOn));
    }

    return(
        <button onClick={handleClick}>
            {isToggleOn ? "켜짐" : "꺼짐"}
        </button>
    );
}

함수 컴포넌트 내부에서 이벤트 핸들러를 정의하는 방법

위 코드처럼

  • 함수 안에 또 다른 함수로 정의하는 방법
  • arrow function을 사용하여 정의하는 방법
    두 가지가 있습니다.

함수 컴포넌트에서는 this를 사용하지 않고 위의 코드처럼 onClick에 곧바로 handleClick을 넘기면 됩니다.

0개의 댓글