1) React 이벤트 이름은 소문자 대신 camelCase를 사용
2) JSX에 문자열 대신 함수를 전달
html에서는 아래와 같이 이벤트를 넣었다면,
<button onclick="activateButton()">클릭하세요</button>
React에서는 이벤트이름을 onClick
, onSubmit
등과 같이 camelCase로 설정한다는 차이점이 있습니다. event handler는 JSX 표기인 { }를 사용하여 연결합니다.
<button onClick={activateButton}>클릭하세요</button>
DOM 요소에만 이벤트 설정이 가능합니다. div
, button
, input
, form
, span
등의 DOM 요소에는 이벤트 설정이 가능하지만, 리액트의 컴포넌트에는 불가능합니다. 예를 들면 위와 같이 button
이라는 DOM 요소에 이벤트를 설정했는데, 아래처럼 <Component>
라는 리액트 컴포넌트에는 onClick을 달아서 우리가 의도한대로 이벤트가 실행되지 않습니다. 그냥 color, name과 같은 props를 전달해주는 것에 불과합니다.
function App() {
const sayHi = function () {
alert('hello');
}
return (
<>
<Component onClick={sayHi} name="홍길동" nickname="홍홍" />
</>
);
}
onClick
과 같이 on
접두사를 지정합니다. handleClick
과 같이 handle
접두사를 지정합니다.<Component onClick={handleClick}/>
정리하면!
on
접두사가 붙었을때는 이 prop에 실제 이벤트가 연결되어 있으며,handle
접두사가 붙은건 이벤트가 발생했을 때 호출되는 실제 function을 의미한다고 생각하면 됩니다.
stopPropagation()
와 preventDefault()
를 포함해서 인터페이스는 브라우저의 고유 이벤트와 같지만 모든 브라우저에서 동일하게 동작한다. 모든 합성 이벤트 객체는 다음 어트리뷰트를 가진다.boolean bubbles
boolean cancelable
DOMEventTarget currentTarget : 이벤트가 바인딩된 요소
boolean defaultPrevented
number eventPhase
boolean isTrusted
DOMEvent nativeEvent : 브라우저 내장 이벤트 객체
void preventDefault() : 링크나 폼 전송과 같은 기본 동작을 방지
boolean isDefaultPrevented()
void stopPropagation() : 이벤트 전파 중단
boolean isPropagationStopped()
void persist()
DOMEventTarget target : 이벤트가 실제로 발생한 요소
number timeStamp
string type
이와 함께 React에서 지원하는 DOM이벤트는
Mouse 이벤트:
onClick onContextMenu onDoubleClick onDrag onDragEnd
onDragEnter onDragExit
onDragLeave onDragOver onDragStart onDrop onMouseDown
onMouseEnter onMouseLeave
onMouseMove onMouseOut onMouseOver onMouseUp
Form 이벤트:
onChange onInput onInvalid onReset onSubmit
이것말고도 종류는 많으니 필요할때 React 공식문서를 참고해보면 좋을 것 같다.
리액트에서 전달되는 event 객체는 W3C 명세에 따라 SyntheticEvent를 정의하기 때문에 순수 자바스크립트에서 사용하는 것과 동일하게 사용하면 된다.
예를 들어, 새로운 페이지로 연결되는 기본 링크 동작을 막으려면 html에서는 이렇게 사용했습니다.
<a href="#"
onclick="console.log('the link is clicked');"
return false> 클릭하세요 </a>
그러나 React에서는 preventDefault()
를 사용합니다.
function App () {
function handleClick(e) {
e.preventDefault();
console.log('the link is clicked');
}
return (
<a href="#" onClick={handleClick}>클릭하세요</a>
)
}
보통 form에서 input을 받을때 콘솔창에 출력해볼때 자주 사용합니다. form의 onSubmit으로 이벤트를 수행하면 바로 새로운 페이지로 연결되기에 콘솔창을 제대로 확인할 수 없기때문이죠.
const handleSubmit = (e) => {
e.preventDefault();
console.log(text);
};
e.preventDefault()
와e.stopPropagation()
의 차이점은?
e.preventDefault()
는 고유 동작을 중단시키고,e.stopPropagation()
은 상위 엘리먼트들로의 이벤트 전파를 중단시킵니다
자바스크립트에서도 이벤트 버블링, 캡쳐링을 본 적이 있다. 다시 정리해보면
이벤트 버블링이랑 특정한 요소에서 어떤 이벤트가 발생했을 때, 상위에 있는 요소까지 이벤트가 전파되는 것을 말한다
버블링은 이벤트가 발생한 요소를 기준으로 가까운 상위 요소부터 하나씩 탐색한다면, 이벤트 캡쳐링은 이벤트가 발생한 요소의 최상위 요소부터 처음 이벤트가 발생한 요소까지 탐색한다
import React, { useState } from "react";
import "../styles.css";
const Banner = () => {
const [isVisible, setIsVisible] = useState(true);
const getAlert = () => alert("ALERT!!");
const closeBanner = () => setIsVisible(false);
return (
<div
className="banner"
onClick={getAlert}
style={{ display: isVisible ? "flex" : "none" }}
>
이 곳을 클릭해서 alert창 띄우기
<button className="close-btn" onClick={closeBanner}>
닫기
</button>
</div>
);
};
export default Banner;
닫기 버튼을 눌렀을 때 딱 배너만 닫고 싶은데, alert
창도 함께 뜬 후에 닫히는 것을 확인할 수 있다. 닫기 버튼의 onClick
이벤트가 발생하면 closeBanner
함수가 실행되고, 상위요소에도 onClick
이벤트가 등록되어있기 때문에 getAlert
함수도 실행되게 되는 것이다.
** 이때 바로 stopPropagation()
을 사용할 수 있다. 특정한 요소에 특정한 이벤트만 실행하고, 전파되는 것을 막고 싶을 때 적용하면 된다.
const closeBanner = (e) => {
e.stopPropagation(); //이벤트 전파 방지
setIsVisible(false);
}
React에서는 DOM엘리먼트가 생성된 후 리스너를 추가하기 위해 addEventListener를 호출할 필요가 없다. 대신, 엘리먼트가 처음 렌더링될때 리스너를 제공하면 된다.
아래의 예시를 보면, 이벤트핸들러를 클래스의 메서드로 만들었고 이를 컴포넌트 내에서 사용할 수 있다.
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
//콜백에서 `this`가 작동하려면 바인딩을 해줘야한다
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(state => ({
isToggleOn: !state.isToggleOn
}));
}
render() {
return (
//render() 함수 안에서 this 값은 render() 함수가 속한 컴포넌트를 가리킴
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}
- 여기에서 주의해야할 것은, JS의 클래스 메서드는 기본적으로 바인딩되어있지 않기 때문에 this.handleClick을 바인딩하지 않고 호출하면 this는 undefined가 된다. 따라서 바인딩해주고 사용해야한다.
- 즉 binding을 따로 지정해주지 않으면 해당 메서드에서 호출하는 this가 해당 클래스안에서의 event값이 아닌 최상위의 window에서 값을 가져올 수 있기 때문에 주의해야한다.
매번 bind을 호출하는 것이 불편하다면, 아래와 같이 클래스필드를 사용하여 콜백을 올바르게 바인딩할 수 있다.
class Test extends React.Component {
// 이 문법은 `this`가 handleClick 내에서 바인딩되도록 합니다.
handleClick = () => {
console.log(this);
}
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
);
}
}
일단 함수형 컴포넌트의 상태값은 useState
훅으로 관리되기 때문에 컴포넌트의 this
로부터 자유롭습니다. 또한 함수형 컴포넌트 자체와 함수형 컴포넌트 안에서 선언한 함수들 모두 전역 객체를 this
로 가지기 때문에 애초에 this
가 다 같습니다. 그래서 이벤트 핸들러에 콜백 함수를 넘기는 상황에도 딱히 신경 쓸 필요가 없습니다.
const
키워드 + 함수 형태로 선언해야한다this
가 따로 필요하지 않다import React, {useState} from "react";
function NumberTest() {
const [num, setNumber] = useState(0);
const increase = () => {
setNumber(num+1);
}
return (
<div>
<h1>Number Test</h1>
<h2>{num}</h2>
<button onClick={increase}>증가</button>
</div>
);
}
export default NumberTest;
상위 컴포넌트 App
에서 하위 컴포넌트인 PhoneForm
의 input값을 전달받아서 그 값을 콘솔에 출력해보자.
이 예시에서 크게 두가지를 중점으로 다뤄보겠습니다.
1. 상위컴포넌트로 데이터 전달하는 과정
2. 여러개의 input 입력받기 (하나의 useState
만으로 구현)
//App.js(상위컴포넌트)
import React from "react";
import PhoneForm from "./PhoneForm";
const App = () => {
const handleCreate = (data) => {
console.log(data);
}
return (
<div className="App">
<PhoneForm onCreate={handleCreate}/>
</div>
)
}
export default App;
//PhoneForm.js(하위 컴포넌트)
const PhoneForm = ({onCreate}) => {
const [inputs, setInputs] = useState({
name: "",
phone: "",
})
const { name, phone } = inputs;
const handleChange = (e) => {
{ name, value } = e.target;
setInputs({
...inputs, //이전의 inputs을 복사(spread operator)
[name]: value, //새로운 값만 덮어쓰여짐
});
}
const handleSubmit = (e) => {
e.preventDefault();
onCreate(inputs);
}
return (
<div>
<form onSubmit={handleSubmit}>
<input name="name" value={name} onChange={handleChange}/>
<input name="phone" value={phone} onChange={handleChange}/>
<button type="submit">등록</button>
</form>
</div>
)
}
export default PhoneForm;
1. 상위컴포넌트로 데이터 전달하는 과정
1) PhoneForm
에서 입력 받은 input
값을 onChange
를 사용해서 값의 변화가 생길 때 마다 자신의 state
에 동기화한다.
2) App
의 handleCreate
메소드를 PhoneForm
의 onCreate
라는 props
로 전달한다.
3) 사용자가 등록 버튼을 누르면(onSubmit 하면) e.preventDefault()
로 페이지전환을 막고, props
로 전달된 onCreate(=handleCreate)
함수의 매개변수로 state
객체를 넘긴다.
4) 전달 받은 하위 컴포넌트의 state 객체를 App의 내부 메소드인 handleCreate에서 사용할 수 있다.
2. 여러개의 input 입력받기
form에서 많은 input 입력을 받게 되는 경우
const [name, setName] = useState("");
const [phone, setPhone] = useState("");
이와 같이 불필요한 중복과정이 일어나므로, useState
에 문자열이 아닌 객체를 넣어주면서 해결할 수 있다.
1) useState에 문자열이 아닌 객체를 넣어준다
2) 구조분해할당을 이용해 inputs 객체의 값을 name, phone에 할당해준다.
const { name, phone } = inputs;
3) ...inputs
처럼 spread operator을 이용해 과거의 값을 복사하고, 바뀐 값만 [e.target.name]: e.target.value
갱신해준다.
[References]
react 이벤트 처리 관련
https://kellis.tistory.com/110
this와 bind 관련
https://blueshw.github.io/2017/07/01/arrow-function/
https://maxkim-j.github.io/posts/react-component-this
컴포넌트간 데이터 교환 관련
https://kellis.tistory.com/110