> For the complete documentation index, see [llms.txt](https://yamoo9.gitbook.io/learning-react-app/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://yamoo9.gitbook.io/learning-react-app/learning-history/event-binding.md).

# Event Binding

React에서 이벤트를 연결하는 방법은 DOM 이벤트 핸들링 방식과 유사합니다. 다만 몇 가지 차이점이 있습니다.

| **차이점**                          | **예시**                                          |
| -------------------------------- | ----------------------------------------------- |
| 이벤트 속성 이름은 **camelCase** 형식으로 작성 | `onClick`, `onKeyDown`, `onFocus`, `onChange` 등 |
| 이벤트 리스너 연결은 JSX 인터폴레이션           | `<button onClick={ this.handleClick } />`       |
| 브라우저 기본 동작 방지                    | `event.preventDefault()` 메서드만 허용                |

{% hint style="info" %}

#### React의 합성 이벤트

React의 이벤트 리스너는 모든 브라우저에서 동일한 이벤트를 처리하기 위한 이벤트(events) 래퍼 객체를 전달받습니다. [합성 이벤트(Synthetic events)](https://ko.reactjs.org/docs/events.html) 객체는 preventDefault, stopPropagation 메서드를 포함하며, 인터페이스는 브라우저의 표준 이벤트 모델과 동일합니다.
{% endhint %}

## DOM 이벤트 바인딩 <a href="#dom-event-binding" id="dom-event-binding"></a>

HTML 요소의 표준 이벤트 속성 값으로 이벤트 리스너를 연결합니다.

```markup
<button onclick="handleEventBinding()">연결 된 DOM 이벤트</button>
```

## React 이벤트 바인딩 <a href="#react-event-binding" id="react-event-binding"></a>

React 이벤트 연결은 이벤트 속성 이름 표기는 camelCase 방식으로, 이벤트 리스너 연결은 함수 참조 또는 인라인 함수를 작성해 JSX에 인터폴레이션 합니다.&#x20;

{% tabs %}
{% tab title="React JSX" %}

```jsx
<button onClick={ handleEventBinding }>연결 된 React 합성 이벤트</button>

// 또는  

<button onClick={(e) => console.log(' ... ')}>인라인 이벤트 바인딩</button>
```

{% endtab %}

{% tab title="React API — createElement" %}

```jsx
React.createElement(
  'button',
  {
    onClick: handleEventBinding,
    children: ['연결 된 React 합성 이벤트']
  }
)
```

{% endtab %}
{% endtabs %}

{% hint style="warning" %}

#### 이벤트 연결 그리고 접근성

특별한 상황이 아닌 경우, HTML 요소 중 포커스(초점) 이동이 가능한 요소에 이벤트를 연결해야 합니다.

* HTML**Input**Element
* HTML**Select**Element
* HTML**TextArea**Element
* HTML**Anchor**Element
* HTML**Button**Element
* HTML**Area**Element

이 외의 요소 특히 \<div />, \<span /> 등 포커스 이동이 가능하지 않은 요소에 이벤트를 연결하면 안됩니다. 다만, [이벤트 위임(event delegation)](https://ko.javascript.info/event-delegation)을 목적으로 이벤트를 연결했다면 내부에 포커스 이동이 가능한 요소를 포함해야 합니다. 그렇지 않으면, 키보드 만으로 서비스를 동일하게 이용할 수 없어 [접근성](https://developer.mozilla.org/ko/docs/Web/Accessibility/Understanding_WCAG/Keyboard) 문제가 됩니다.
{% endhint %}

## 브라우저 기본 동작 방지 <a href="#prevent-browser-default" id="prevent-browser-default"></a>

특정 동작으로 작동하길 기대 할 경우, 브라우저에 설정 된 기본 동작이 작동되지 않도록 설정해야 합니다. React는 이벤트 속성에 연결 된 이벤트 리스너가 전달 받는 합성 이벤트의 `preventDefault()` 메서드를 사용해 방지할 수 있습니다.

```jsx
const AppleMusicLink = () => {

  // 이벤트 리스너(함수)
  function handleClick(e) {
    e.preventDefault() // 브라우저 기본 동작 방지
    console.log('브라우저 기본 동작을 방지했습니다.')
  }
  
  return (
    <a 
      href="https://apple.com/kr/apple-music"
      rel="noopener noreferrer"
      target="_blank"
      onClick={ handleClick }
    >
      애플 뮤직
    </a>
  )
  
}
```

{% hint style="danger" %}
DOM 방식의 브라우저 기본 동작 방지는 React에서 작동하지 않습니다.

```markup
<a href="#" onclick="console.log('...'); return false">DOM 방식</a>
```

{% endhint %}

## 클래스 컴포넌트의 this <a href="#this-of-the-class-component" id="this-of-the-class-component"></a>

클래스 컴포넌트를 사용 할 경우, 클래스 인스턴스 메서드의 `this` 참조에 대한 주의가 필요합니다. 클래스 컴포넌트에서 작성 된 handleClick 인스턴스 메서드 안에서 `this`가 가리키는 참조를 확인해보면 기대와 달리 `undefined`가 출력됩니다. 왜 `this` 는 클래스로부터 생성 된 인스턴스를 가리키지 않는 것일까요?

```jsx
import React from 'react'


class AppleMusicLink extends React.Component {
  
  // 인스턴스 메서드
  handleClick(e) {
    e.preventDefault()
    console.log(this) // this === undefined
  }

  render() {
    return (
      <a 
        href="https://apple.com/kr/apple-music"
        rel="noopener noreferrer"
        target="_blank"
        onClick={ this.handleClick }
      >
        애플 뮤직
      </a>
    )
  }
  
}
```

이 문제는 React의 문제가 아니라, [JavaScript에서 함수가 작동하는 방식](https://www.smashingmagazine.com/2014/01/understanding-javascript-function-prototype-bind/)에 원인이 있습니다. 객체의 메서드가 아닌, 일반 함수로 실행 될 경우 `this` 참조는 엄격 모드(`'use strict'`)에서의 결과처럼 `undefined`가 됩니다.

```javascript
(/* IIFE */ () => {
  'use strict'

  function whatDoesThisRef() { console.log(this) }
  const o = { whatDoesThisRef }

  // 일반 함수 실행 결과: undefined
  console.log( whatDoesThisRef() )
  
  // 객체의 메서드 실행 결과: o { whatDoesThisRef }
  console.log( o.whatDoesThisRef() )

})()
```

## 클래스 컴포넌트의 this 바인딩 <a href="#this-binding-of-class-component" id="this-binding-of-class-component"></a>

클래스 컴포넌트에서 이벤트 속성에 연결 된 메서드의 `this` 참조가 `undefined`인 경우, 기대한 대로 앱이 작동하지 않습니다. 이 문제를 해결할 수 있는 다양한 방법을 살펴보겠습니다.

### 1. this 참조 변경 <a href="#id-1-binding-this" id="id-1-binding-this"></a>

JavaScript 함수는 [bind()](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) 메서드를 사용해 `this` 참조를 임의로 변경할 수 있습니다. 이 방법을 사용하여 메서드의 `this` 참조가 컴포넌트 인스턴스를 가리키도록 변경할 수 있습니다.

```jsx
import React, { Component } from 'react'


class AppleMusicLink extends Component {

  constuctor(props) {
    super(props)
    
    // this 참조를 컴포넌트 인스턴스로 변경
    this.handleClick = this.handleClick.bind(this)
  }
  
  handleClick(e) {
    e.preventDefault()
    console.log(this) // this === AppleMusicLink {}
  }
  
  render() {
    return (
      <a 
        href="https://apple.com/kr/apple-music"
        rel="noopener noreferrer"
        target="_blank"
        onClick={ this.handleClick }
      >
        애플 뮤직
      </a>
    )
  }
  
}
```

{% hint style="info" %}

#### 인라인 this 바인딩

클래스 생성자(constructor)에서 this 참조를 변경하지 않고, 인스턴스 메서드 참조에 직접 bind()를 사용해 this 참조를 인스턴스로 변경하는 방법을 활용할 수 있습니다.

```jsx
class AppleMusicLink extends Component {

  handleClick(e) { /* ... */ }

  render() {
    return (
      <a 
        href="https://apple.com/kr/apple-music"
        rel="noopener noreferrer"
        target="_blank"
        onClick={ this.handleClick.bind(this) }
      >
        애플 뮤직
      </a>
    )
  }
}
```

{% endhint %}

### 2. 화살표 함수 활용 <a href="#id-2-using-arrow-function" id="id-2-using-arrow-function"></a>

인스턴스 메서드를 래핑 하는 화살표 함수를 사용하면 `this` 참조를 인스턴스로 설정할 수 있습니다.

```jsx
import React, { Component } from 'react'


class AppleMusicLink extends Component {
  
  handleClick(e) {
    e.preventDefault()
    console.log(this) // this === AppleMusicLink {}
  }
  
  render() {
    return (
      <a 
        href="https://apple.com/kr/apple-music"
        rel="noopener noreferrer"
        target="_blank"
        onClick={ (e) => this.handleClick(e) }
      >
        애플 뮤직
      </a>
    )
  }
  
}
```

### 3. 클래스 필드 선언 <a href="#id-3-class-field" id="id-3-class-field"></a>

ES 표준에 제안 된 [클래스 필드 선언](https://github.com/tc39/proposal-class-fields)을 사용해 문제를 해결할 수 있습니다. 향상된 객체 표기법 대신 화살표 함수 방식으로 메서드를 정의하면 `this` 참조가 인스턴스를 가리키게 됩니다.

```jsx
import React, { Component } from 'react'


class AppleMusicLink extends Component {
  
  // 클래스 필드 선언 방식으로 인스턴스 메서드 정의
  handleClick = (e) => {
    e.preventDefault()
    console.log(this) // this === AppleMusicLink {}
  }
  
  render() {
    return (
      <a 
        href="https://apple.com/kr/apple-music"
        rel="noopener noreferrer"
        target="_blank"
        onClick={ this.handleClick }
      >
        애플 뮤직
      </a>
    )
  }
  
}
```

## 이벤트 리스너에 인자 전달 <a href="#forwarding-factors-to-event-listeners" id="forwarding-factors-to-event-listeners"></a>

인스턴스 메서드(이벤트 리스너)에 특정 인자를 전달하는 방법은 다음의 2가지입니다.

### 1. 화살표 함수 활용 <a href="#id-1-using-arrow-func" id="id-1-using-arrow-func"></a>

인스턴스 메서드를 래핑하는 화살표 함수를 이벤트 속성에 연결한 후, 특정 인자를 전달할 수 있습니다.

```jsx
list.map(item => (
  <a 
    key={item.id} 
    href={item.link} 
    onClick={(e) => this.handleClick(item.id, e)}
  >
    아이템 ID, 합성 이벤트 객체를 이벤트 리스너에 전달
  </a>
))
```

### 2. 함수의 bind 활용 <a href="#id-2-using-function-bind" id="id-2-using-function-bind"></a>

인스턴스 메서드의 `this` 참조를 `bind()` 메서드를 사용해 인스턴스로 변경한 후, 특정 인자를 전달합니다.

```jsx
list.map(item => (
  <a 
    key={item.id} 
    href={item.link} 
    onClick={this.handleClick.bind(this, item.id)}
  >
    아이템 ID를 이벤트 리스너에 전달
  </a>
))
```

## 이벤트 위임 대상 변경 <a href="#change-event-delegation-target" id="change-event-delegation-target"></a>

React 16 버전까지는 document 노드에 이벤트를 연결해 위임했습니다. 하지만 React 17 버전부터는 React 앱의 루트 DOM 노드에 이벤트를 연결해 위임합니다. (일반적인 경우 `#root` DOM 요소 노드)

```jsx
ReactDOM.render(
  // React 가상 DOM 트리
  <App />,
  // 실제 DOM 요소 → 루트 DOM 컨테이너
  document.getElementById('root')
);
```

![React 17버전부터 변경된 이벤트 위임 대상](/files/-MV3iOuMH-jBq2Vutofd)

{% hint style="info" %}
React 17 버전은 향후 [점진적인 업데이트](https://reactjs.org/blog/2020/10/20/react-v17.html)를 목적으로 기존의 이벤트 시스템 방식을 다소 변경했습니다.
{% endhint %}

이러한 변경으로 React 17 버전부터는 다음과 같은 경우에 더욱 안전하게 React를 사용할 수 있게 되었습니다.

1. 다양한 버전의 React가 공존하며 DOM에 앱을 생성한 경우
2. 다른 기술로 빌드 된 앱의 일부에 React를 적용 할 경우

{% hint style="info" %}

#### 포털(Portals) 사용에는 문제가 없을까요?

[Portals](https://ko.reactjs.org/docs/portals.html)은 React에서 이벤트를 리스닝하므로 문제가 발생하지 않습니다. 안심하고 사용해도 됩니다.
{% endhint %}

## 이벤트 위임이란? <a href="#what-is-event-delegation" id="what-is-event-delegation"></a>

특정 노드에 일일이 이벤트 리스너를 추가하는 대신, 이벤트 리스너를 특정 노드들을 포함하는 상위 노드에 연결하여 이벤트를 전파하는 것을 **이벤트 위임**이라고 합니다. 위임된 이벤트는 포함된 하위 노드에 전파됩니다.&#x20;

이벤트가 전파되는 방식의 기본 값은 버블링(bubbling)이며, 캡처링(capturing) 방식으로 변경해 사용할 수도 있습니다.

```javascript
<ul class="parentNode">
  <li class="childNode"><a href="/child-node-1">하위 노드 1</a></li>
  <li class="childNode"><a href="/child-node-2">하위 노드 2</a></li>
  <li class="childNode"><a href="/child-node-3">하위 노드 3</a></li>
</ul>
```

```javascript
// 상위(부모) 노드
const parentNode = document.querySelector(".parentNode")

// 상위 노드에 이벤트 리스너 연결
parentNode.addEventListener('click', (e) => {
  const nodeName = target.nodeName.toLowerCase()
  // 하위 노드에 이벤트 전파 
  // (해당 대상 노드에 처리 코드 작성)
  switch(nodeName) {
    case 'li': 
      console.log('<li> 노드 클릭') 
      break
    case 'a': 
      // 이벤트 기본 동작 차단
      e.preventDefault()
      console.log('<a> 노드 클릭') 
      break
    defualt:
      console.log(`${nodeName} 노드 클릭`)
  }
})
```

만약 이벤트 위임을 사용할 수 없었다면? 하위 노드 들이 실시간으로 추가 또는 제거 될 때마다 이벤트 리스너를 매번 연결하거나 제거해야 하니 끔찍했을 것입니다. 이벤트 위임을 사용할 수 있기에 새로운 노드가 추가, 제거 또는 업데이트 되어도 별도로 이벤트 리스너를 연결하거나 제거하지 않아도 됩니다.

```markup
<ul class="parentNode">
  <li class="childNode"><a href="/child-node-1">하위 노드 1</a></li>
  <!-- 실시간 제거된 노드 -->
  <!-- <li class="childNode"><a href="/child-node-2">하위 노드 2</a></li> -->
  <li class="childNode"><a href="/child-node-3">하위 노드 3</a></li>
  <!-- 실시간 추가된 노드 -->
  <li class="childNode"><a href="/child-node-3">하위 노드 4</a></li>
  <li class="childNode"><a href="/child-node-3">하위 노드 5</a></li>
  <li class="childNode"><a href="/child-node-3">하위 노드 6</a></li>
</ul>
```
