Let's Write_ 프론트엔드

React - event handler 이벤트 핸들러의 e 는 어떻게 전달되는 걸까?

TIL

처음 공부를 시작할 때 이해가 잘 안 되는 부분이 있었는데 바로 이벤트 핸들러 함수를 정의할 때의 e 였다.

 

예를 들면 아래에서의 handleSubmit(e) 함수 정의가 있다.

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

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

 

onSubmit에서 e는 등장한 적이 없는데 handleSubmit 은 대체 어떻게 e를 받는 거지?

 

이런 의문을 나만 가졌을 리 없다.

 

누군가에게 도움이 되길 바라며 적는 글이다.

 

[TOC]
# 1 . 결론
# 2. 자바스크립트
## 2.1 addEventListener
## 2.2 DOM Spec
## 2.3 listener callback
# 3. React
## 3.1 React listener
## 3.2 Synthetic Event

 

 

결과적으로 기억할 내용은 다음이다.

# 1. 결론

React 에서 JSX 에 이벤트핸들러로 넘기는 이벤트리스너함수(콜백)는 return/render 전에 정의하고, 이벤트에 걸리는 이벤트 리스너는 인자로  해당synthetic event 를 받을 수 있다.

 

이유에 대한 결론은 만들어진 곳에서 넘겨받은 함수는 첫번째 인자로 e(이벤트) 를 받아 호출할 수 있게 정의해 뒀기 때문이다.

호출되는 곳에서는 콜백함수에 인자로 이벤트 객체를 넘기고 넘겨질 콜백함수는 개발자가 정의, 선언하는데
그 때 정의 하는 인자의 네이밍은 e, event,evt 등등 쓰는 사람 마음대로 적혀지곤 한다.
그러나 최고의 네이밍은 누가 봐도, 코딩을 모르는 사람이 봐도 읽을 수는 있는 event 로 적는 것이 가독성과 협업성에 좋으니까 event로 는게 좋다.

 

라고 경험 + 공식문서 내용으로 알 수 있는데 좀 더 파헤쳐 보자.

 

React는  javascript 라이브러리 라는 걸 먼저 기억하자.

 

# 2. 자바스크립트

## 2.1 addEventListener

 

javascript 에서는

EventTarget.addEventListener()

를 이용해서 돔에서 발생하는 이벤트를 잡아 콜백 함수를 설정할 수 있다.

(EventTarget의 addEventListener() 메서드는 지정한 이벤트가 대상에 전달될 때마다 호출할 함수를 설정합니다.)

 

참고로 콜백 함수란, 함수의 인자로 보낼 수 있는 함수로 생각하면 이해하기 쉽다.

 

사용 구문은 다음과 같다.

target.addEventListener(type, listener[, options]);
target.addEventListener(type, listener[, useCapture]);

 

1. 사용할 때 필수적으로 와야 하는 첫 번째 인자 type이란

이벤트의 유형으로 'click', 'submit', 'focus'  등 w3c에서 정의한  이벤트 종류이다.

 

2.  필수적으로 와야 하는 두 번째 인자 listner

앞에서 정의한 이벤트 type의 이벤트가 발생했을 때 실행될 콜백 함수로,

('click' -> 'click' event)를 첫 번째 인자로 받는 함수이다. => 요 리스너가 리액트의 리스너와 같은 역할이다.

 

그리고 관련하여 돔 명세를 봐보자


## 2.2 DOM Spec

obj.addEventListener("load", imgFetched)

function imgFetched(ev) {
  // great success
  …
}
 In the example above ev is the event. ev is passed as an argument to the event listener’s callback (typically a JavaScript Function as shown above). Event listeners key off the event’s type attribute value ("load" in the above example). The event’s target attribute value returns the object to which the event was dispatched (obj above).

 

돔명세에 의하면 이벤트 리스너의 콜백에는 이벤트가 소괄호 안에 argument 로 전달된다.

e든 ev든 evt, event 든 identifier 일 뿐 그 자리에는 이벤트가 들어온다.

 


## 2.3 listener callback

addEventListener 에 오는  콜백함수 형태를 보면

1. function(e) { ...}

2. function(){...}

이런 익명함수들인데,

인자가 없는 건, 함수 내에서 e 를 참조할 필요가 없을 때 굳이 넘겨받지 않아도 되기때문에 인자로도 안받는 것이다.

이 부분이 포인트다.

 

이 부분의 이해를 돕는 예시를 만들어 봤다.

function log(a) {
    console.log(a)
}
log('test') // test
log('test', 2) // test

function alarm(msg){
  alert("!");
}
alarm(msg) // !
alarm() // !

function alarm2(){
  alert("!");
}
function alarm2(123) // !

 

그리고 event.target 은 이벤트가 발생한 객체이다.

 

 이벤트 타겟의 명세는 다음과같다

 

2.7. Interface EventTarget

[Exposed=(Window,Worker,AudioWorklet)]
interface EventTarget {
  constructor();

  undefined addEventListener(DOMString type, EventListener? callback, optional (AddEventListenerOptions or boolean) options = {});
  undefined removeEventListener(DOMString type, EventListener? callback, optional (EventListenerOptions or boolean) options = {});
  boolean dispatchEvent(Event event);
};

callback interface EventListener {
  undefined handleEvent(Event event);
};

dictionary EventListenerOptions {
  boolean capture = false;
};

dictionary AddEventListenerOptions : EventListenerOptions {
  boolean passive = false;
  boolean once = false;
  AbortSignal signal;
};

 

 

# 3. React

자바스크립트에서는 addEventListener를 통해서 이벤트 핸들러를 구현하지만, 리엑트에서는 다르다. 


## 3.1 React listener

When using React, you generally don’t need to call  'addEventListener' to add listeners to a DOM element after it is created. Instead, just provide a listener when the element is initially rendered.

React에서는  DOM element가 생성된 "후"에 리스너를 추가하기 위해 addEventListener를 호출할 필요가 없습니다.  처음 렌더링 될 때 리스너를 제공합니다.

이는 return 위에서 정의한 handleSubmit 함수의 위치를 의미하는 듯하다.

다시 말하자면 리스너는 이벤트가 발생할 때 실행될 콜백 함수이다.

이 콜백 함수를 return 위에서 작성해 주는 것이 react의 방식이다.


## 3.2 Synthetic Event

리액트에서는 합성이벤트 (Synthetic Event) 라는 리액트 이벤트를 사용하는데 이건 브라우저에서 기본적으로 제공하는 

native event 를 참고해서 리액트에서 재가공한 이벤트 객체이다.

이벤트 핸들러는 모든 브라우저에서 이벤트를 동일하게 처리하기 위한 이벤트 래퍼 SyntheticEvent 객체를 전달받습니다. 
Although events are typically dispatched by the user agent as the result of user interaction or the completion of some task, applications can dispatch events themselves by using what are commonly known as synthetic events:
이벤트는 일반적으로 사용자 상호 작용 또는 일부 작업 완료의 결과로 사용자 에이전트에 의해 전달되지만 어플리케이션은 일반적으로 합성 이벤트로 알려진 것을 사용하여 이벤트를 전달할 수 있습니다.

// add an appropriate event listener
obj.addEventListener("cat", function(e) { process(e.detail) })
// create and dispatch the event  [ synthetic event ]  <- 요걸 리액트에서 쓴다
var event = new CustomEvent("cat", {"detail":{"hazcheeseburger":true}}) obj.dispatchEvent(event)

 

따라서 JS DOM 에 정의된 이벤트와는 정확히 일치하지는 않는다. 

 

 

 

 

https://developer.mozilla.org/ko/docs/Web/API/EventTarget/addEventListener

https://developer.mozilla.org/ko/docs/Web/API/EventListener

https://developer.mozilla.org/en-US/docs/Web/API/EventListener/handleEvent

https://ko.reactjs.org/docs/events.html 

 

https://dom.spec.whatwg.org/#introduction-to-dom-events

https://dom.spec.whatwg.org/#interface-eventtarget

 

 

 

react - class, JSX 콜백함수에서 this

TIL

React를 사용할 때 일반적으로 DOM 요소가 생성된 후에 리스너를 추가하기 위해 addEventListener 를 호출할 필요가 없습니다.

대신 요소가 처음 렌더링될 때 리스너를 제공합니다.

ES6 class 를 이용해 요소를 정의할 때 이벤트 핸들러의 일반적인 패턴은 클래스의 메서드 형태입니다. 

 

JSX 콜백에서 this 의 의미에 대해 주의해야합니다.

자바스크립트에서 클래스 메서드는 기본적으로 bound 되지 않습니다. 만약 constructor 내부에서 

this.handleClick = this.handleClick.bind(this) 바인드를 잊은채로 onClick 에 전달하면, this 는 함수가 실제로 호출될 때 undefined 로 취급됩니다.

 

이건 React에서 정의한 동작이 아닙니다. 자바스크립트의 함수의 동작 방식 의 일부입니다.

일반적으로 onClick={this.handleClick} 처럼 () 없이 메서드를 참조하면, 그 메서드를 bind 해야합니다.

만약 bind 를 호출하는 게 귀찮은 경우 이 문제를 해결할 수 있는 두가지 방법이 있습니다.

 

1. 만약 실험 기능인 퍼블릭 클래스 필드 문법 을 사용한다면 클래스 필드를 정확히 콜백에 bind할 수 있습니다.

- 함수선언에서 화살표함수를 사용

 

2. JSX에서 이벤트에 거는 콜백함수에서 화살표함수를 사용

이경우는 바인드를 하지 않아도 되지만, 해당 컴포넌트를 렌더링 할때마다 서로 다른 콜백이 만들어진다.

. 대부분의 경우에서 크게 문제는 없습니다. 하지만 만약 콜백에서 하위 컴포넌트에 prop을 전달하는 경우, 이 컴포넌트는 큰 비용으로 다시 렌더링될 수 있습니다. 이런 종류의 성능 문제를 피하기 위해 보통

 

생성자 함수에서 바인딩하거나

클래스 필드 문법을 사용하는 걸 권장합니다.

 

https://reactjs-kr.firebaseapp.com/docs/handling-events.html