Event handling은 적합한 JSX 태그에 handler function을 prop으로 전달하여 구현할 수 있다.
export default function Button() {
function handleClick() {
alert('You clicked me!');
}
return (
<button onClick={handleClick}>
Click me
</button>
);
}
<button onClick={() => {
alert('You clicked me!');
}}>
첫 예시와 같이 함수를 정의하여 전달할 수 있으며, 다음 예시 같이 inline syntax를 이용하는 것도 가능하다. 그러나 view와 logic을 분리하는 관점에서 봤을 때, inline 방식은 그렇게 좋지 않은 방식임을 주의하자.
또한 naming convention에 따라, handler function의 명명은 이벤트명 앞에 handle
이라는 키워드를 붙여 사용하는 것이 좋다. handleClick
이 그 예시이다.
🚨 Functions passed to event handlers must be passed, not called.
Event handler를 구현할 때, 주의할 점은 handler function의 실행값이 아닌 funciton 그 자체를 prop으로 전달해야 한다는 점이다.
passing a function - <button onClick={handleClick}>
calling a function - <button onClick={handleClick()}>
아래 예시의 경우, button
component의 render와 동시에 handleClick()
함수가 실행된다. 또한 handleClick()
의 반환값은 undefined
인 만큼 버튼 클릭 시에는 오히려 아무런 이벤트가 발생하지 않는다.
이는 inline syntax로 구현했을 때도 마찬가지이다.
passing a function - <button onClick={() => alert('...')}>
calling a function - <button onClick={alert('...')}>
따라서 event handler에 inline function을 이용하고 싶다면, 첫 예시와 같이 anonymous function으로 이벤트 시 발생될 동작을 감싸야 한다는 점을 주의하자.
• Naming event handlers props
<button>
과 <div>
같은 Built-in component에 handler function을 전달하기 위해선 prop의 이름으로 onClick
와 같이 browser event name에 해당하는 이름을 반드시 사용해야 한다.
다시 말해, Built-in component가 아니라면 prop의 이름이 그 무엇이 되어도 상관 없다.
function Button({ onSmash, children }) {
return (
<button onClick={onSmash}>
{children}
</button>
);
}
export default function App() {
return (
<div>
<Button onSmash={() => alert('Playing!')}>
Play Movie
</Button>
<Button onSmash={() => alert('Uploading!')}>
Upload Image
</Button>
</div>
);
}
위 예시의 경우, <button>
에 다양한 event handling을 적용하기 위해 Button
component를 생성하여 handler function을 전달받고 이를 onClick
에 다시 전달하는 형태를 띄고 있다.
앞서 말했다시피, 버튼을 눌렀을 때 이벤트가 발생하기 위해선 반드시 built-in component인 button
의 onClick
prop에 handler function을 전달해야만 한다. 그러나 handler function을 전달하는 중간다리인 Button
component의 경우 어떤 이름으로 handler function을 전달받아도 상관없다.
Button
component의 입장에선 전달받는 handler function도 다른 prop과 다를 점이 전혀 없으므로 onSmash
대신 whatever
이라는 prop명을 사용해도 상관 없지만, 이번에도 역시 convention에 따라 발생하는 이벤트 앞에 on
을 붙여 명명하는 것이 좋다.
Parent component의 event handler가 child component에서 발생한 event까지 감지하여 handler function을 수행할 수 있으며 이를 Event Propagation 혹은 Event Bubbling이라고 한다.
export default function Toolbar() {
return (
<div className="Toolbar" onClick={() => {
alert('You clicked on the toolbar!');
}}>
<button onClick={() => alert('Playing!')}>
Play Movie
</button>
</div>
);
}
Toolbar
component가 render한 버튼을 클릭하면 "Playing"
과 "You clicked on the toolbar!"
두 개의 알림창이 발생한다. 이는 parent component인 <div>
의 handler function이 버튼을 클릭했을 때도 발생하기 때문이다.
🚨 All events propagate in React except
onScroll
, which only works on the JSX tag you attach it to.
• Stopping propagation
Event handler들은 event object를 인자로 받게 되며 해당 object에는 발생한 이벤트와 관련된 정보들이 저장된다.
Event bubbling을 방지하고 싶다면, 전달받은 event object를 e
라고 했을 때, e.stopPropagation()
을 이용할 수 있다. 해당 함수를 사용하면 e
를 발생시킨 event가 부모의 event handler로 전달되는 것을 막을 수 있다.
function Button({ onClick, children }) {
return (
<button onClick={e => {
e.stopPropagation();
onClick();
}}>
{children}
</button>
);
}
export default function Toolbar() {
return (
<div className="Toolbar" onClick={() => {
alert('You clicked on the toolbar!');
}}>
<Button onClick={() => alert('Playing!')}>
Play Movie
</Button>
</div>
);
}
• Capture phase events
Logging 등의 이유로, 특정 element의 child에서 발생한 모든 event를 handle해야하는 경우가 발생한다. 그러나 이때 자식 event handler에서 stopPropagation()
이 실행중이라면 어떻게 해야할까?
이는 event handler명 앞에 Capture
를 붙여 구현할 수 있다.
<div onClickCapture={() => { /* this runs first */ }}>
<button onClick={e => e.stopPropagation()} />
<button onClick={e => e.stopPropagation()} />
</div>
이 경우 <button>
의 event handler에 stopPropagation()
이 실행중임에도 불구하고 onClickCapture()
가 먼저 실행되어 event를 감지할 수 있게 된다.
• Preventing default behavior
Broweser event에는 연관된 특정한 default behavior를 갖고 있는 경우가 있다. 예를 들어, <form>
의 submit event는 자동으로 페이지를 새로 고침한다.
이러한 default behavior는 e.preventDefault()
를 통해 방지할 수 있다.
return (
<form onSubmit={e => {
e.preventDefault();
alert('Submitting!');
}}>