리액트에서는 JSX에 이벤트 핸들러를 추가할 수 있어요. 이벤트 핸들러는 클릭, 호버, 폼 입력창에 포커싱하기와같은 여러 상호작용에 반응하여 동작하는 사용자 정의 함수예요.
이 페이지에서는
- 이벤트 핸들러를 작성하는 다른 방법들
- 부모 컴포넌트에서 이벤트를 처리하는 로직을 어떻게 전달하는지
- 이벤트를 어떻게 전파하고 어떻게 멈추는지
를 알아볼 거예요.
Adding event handlers | 이벤트 핸들러 추가하기
이벤트 핸들러를 추가하려면 먼저 함수를 정의한 후 적절한 JSX 태그에 prop으로 전달해야해요. 아래는 아직 아무것도 하지 않는 버튼이에요.
아래의 세 단계를 통해 사용자가 클릭했을 때 메시지가 보이도록 만들어볼게요.
Button
컴포넌트 안에서handleClick
함수를 선언하세요.- 해당 함수 안에서 로직을 구현하세요. (메시지를 보여줄 때
alert
를 사용하세요.) onClick={handleClick}
을<button>
JSX에 추가하세요.
handleClick
함수를 정의했고 button
에 prop으로 이 함수를 전달했어요. handleClick
은 이벤트 핸들러에요. 이벤트 핸들러는 다음과 같은 특징이 있어요.
- 보통은 컴포넌트 내부에서 정의돼요.
- 이벤트 이름 앞에
handle
을 넣어서 이름을 만들어요.
컨벤션에 따르면 이벤트 핸들러의 이름은 handle
뒤에 이벤트 이름이 오는 구조로 짓는 것이 일반적이에요. 종종 onClick={handleClick}
, onMouseEnter={handleMouseEnter}
등과 같은 이름을 보게 될 거예요.
아니면 JSX 안에서 인라인으로 이벤트 핸들러를 정의할 수 있어요.
<button onClick={function handleClick() {
alert('You clicked me!');
}}>
또는 조금 더 간결하게 화살표 함수를 사용할 수도 있어요.
<button onClick={() => {
alert('You clicked me!');
}}>
이 스타일은 전부 다 동일해요. 인라인 이벤트 핸들러는 짧은 함수에서 편리해요.
함정
이벤트 핸들러에 전달된 함수는 호출되는 것이 아니라 전달되어야만 해요.
예시
올바른 예시 - 함수 전달하기 잘못된 예시 - 함수 호출하기 <button onClick={handleClick}>
<button onClick={handleClick()}>
차이는 아주 미묘해요. 첫 번째 예시에서는handleClick
함수가onClick
의 이벤트 핸들러로 전달돼요. 리액트가 함수를 기억하고 사용자가 버튼을 클릭할 떄만 함수를 호출하하고 말해주는 거예요.
두 번째 예시에서handleClick()
의 뒤에 붙은()
는 클릭을 하지 않았어도 렌더링하는 동안 함수를 즉시 실행해요. JSX의{
과}
안에 있는 자바스크립트는 즉시 실행되기 때문이에요.
코드를 인라인으로 작성할 때, 같은 함정이 다른 방법으로 나타나요.
예시
이와 같이 인라인 코드를 전달하면 클릭할 때 실행되지 않아요. 대신 컴포넌트가 렌더링 될 때마다 실행돼요.
올바른 예시 - 함수 전달하기 잘못된 예시 - 함수 호출하기 <button onClick={() => alert('...')}>
<button onClick={alert('...')}>
// 이 alert는 클릭 될 떄 실행되는 것이 아니라 컴포넌트가 렌더링 될 때 실행돼요. <button onClick={alert('You clicked me!')}>
만약 이벤트 핸들러를 인라인으로 정의하고 싶다면 아래와 같이 익명함수로 감싸세요.<button onClick={() => alert('You clicked me!')}>
이렇게 하면 코드는 렌더링할 때마다 실행하는 대신 추후에 호출되는 함수를 만들어요.두 가지 경우에서 전달해야햐는 것은 함수에요.
<button onClick={handleClick}>
는handleClick
함수를 전달해요.<button onClick={() => alert('...')}>
는() => alert('...')
함수를 전달해요.
Reading props in event handlers | 이벤트 핸들러에서 pops를 읽기
이벤트 핸들러는 컴포넌트 안에서 선언되기 때문에 이 함수들은 컴포넌트의 props에 접근할 수 있어요. 아래 예시는 클리을 하면 message
prop으로 alert 창을 보여주는 버튼이에요.
이는 두 버튼이 다른 메시지를 보여주도록 만들어요. 전달된 메시지를 바꿔보세요.
Passing event handlers as props | props로 이벤트 핸들러 전달하기
종종 부모 컴포넌트가 자식 컴포넌트의 이벤트 핸들러를 지정해요. 버튼을 생각해볼게요. Button
컴포넌트를 사용하는 곳에 따라 다른 함수를 실행하고 싶을 수도 있어요. 하나는 영화를 재생하고 다른 하나는 사진을 업로드해요.
이런 기능을 하기 위해 아래와 같이 이벤트 핸들러로 부모 컴포넌트에게서 받은 prop을 전달하세요.
여기서 Toolbar
컴포넌트는 PlayButton
과 UploadButton
을 렌더링해요.
PlayButton
은handlePlayClick
을onClick
prop으로Button
에 전달해요.UploadButton
은() => alert('Uploading!')
을onClick
prop으로Button
에 전달해요.
마지막으로 Button
컴포넌트는 onClick
이라는 prop을 받아요. 이 prop을 내장된 브라우저의 <button>
에 onClick={onClick}
으로 전달해요. 사용자가 클릭하면 전달받은 함수를 호출하라고 리액트에게 알려준 거예요.
만약 디자인 시스템을 이용한다면 버튼과 같이 스타일은 포함하고 있지만 행동을 지정하지 않는 컴포넌트를 흔히 볼 수 있어요. 대신, PlayButton
이나 UploadButton
과 같은 컴포넌트는 이벤트 핸들러를 아래로 내려요.
Naming event handler props | 이벤트 핸들러 prop의 이름 짓기
<button>
이나 <div>
와 같은 내장 컴포넌트는 onClick
과 같은 브라우저 이벤트 이름만을 지원해요. 그러나 자체 컴포넌트를 만들 때, 해당 컴포넌트의 이벤트 핸들러 prop의 이름을 원하는대로 지을 수 있어요.
컨벤션에 따르면 이벤트 핸들러 prop은 on
으로 시작해야하고 뒤이어 대문자가 와야해요.
예를 들어 Button
컴포넌트의 onClick
prop은 onSmash
라고 이름을 지을 수 있어요.
이 예시에서 <button onClick={onSmash}>
는 브라우저 <button>
(소문자) 버튼은 onClick
이라는 prop이 필요하다는 것을 보여줘요. 그러나 직접 만든 Button
컴포넌트에서 만드는 prop 이름은 여러분에게 달려있어요!
컴포넌트가 여러 상호작용을 지원할 때, 이벤트 핸들러 prop은 앱의 콘셉에 맞는 이름을 가져요. 예를 들어 이 Toolbar
컴포넌트는 onPlayMovie
와 onUploadImage
라는 이벤트 핸들러를 받아요.
어떻게 App
컴포넌트가 Toolbar
가 onPlayMovie
나 onUploadImage
로 무엇을 할 것인지는 알 필요가 없는지에 주목하세요. Toolbar
의 구현 세부사항이 바로 이것이에요. 여기서 Toolbar
는 onClick
핸들러로 이 함수들을 Button
들에 전달해요. 그러나 나중에는 키보드 단축키로 이들을 발동시킬 수 있어요. onPlayMovie
와 같이 앱의 특정한 상호작용에 맞춘 prop 이름을 짓으면 나중에 이 props가 어떻게 사용될지를 바꿀 수 있는 유연성을 가지게 돼요.
노트
이벤트 핸들러에 적절한 HTML 태그를 사용하도록 하세요. 예를 들어. 클릭을 처리하려면<div onClick={handleClick}>
대신에<button onClick={handleClick}>
을 사용하세요. 실제 브라우저의 <button>을 사용하는 것은 키보드 탐색과 같은 브라우저에 내장된 행동을 가능하게 해요. 만약 여러분이 버튼의 기본 스타일링을 좋아하지 않고 링크나 다른 UI 엘리먼트처럼 보이도록 만들고 싶다면 CSS로 만들 수 있어요. 접근 가능한 마크업을 작성하는 방법을 알아보세요.
Event propagation | 이벤트 전파
이벤트 핸들러는 컴포넌트가 갖고 있는 자식 컴포넌트에서 발생한 이벤트를 캐치할 수도 있어요. 이를 이벤트가 "버블링" 또는 "전파" 되어서 트리를 따라 올라간다고 표현해요. 이벤트가 발생된 곳부터 시작해서 트리를 따라 올라가요.
이 <div>
는 버튼을 두 개 가지고 있어요. <div>
와 각각의 버튼은 모두 onClick
핸들러를 가지고 있어요. 버튼이 클릭되면 어떤 핸들러가 실행될 것 같나요?
만약 어떤 버튼을 클릭하면 해당 onClick
이 먼저 실행되고 이어서 부모 <div>
의 onClick
이 실행돼요. 그래서 두 메시지가 모두 보여요. 만약 툴바 자체를 클린한다면 부모 <div>
의 onClick
만 실행돼요.
함정
리액트에서는 모든 이벤트가 전파되는데 onScroll은 해당 이벤트가 첨부된 JSX 태그에서만 동작해요.
Stopping propagation | 전파 멈추기
이벤트 핸들러는 이벤트 객체를 유일한 인자로 받아요. 컨벤션에 따르면 보통은 e
라고 불리고, "events"를 상징해요. 이 객체를 이벤트에 대한 정보를 읽는데 사용할 수 있어요.
이벤트 객체로 전파도 막을 수 있어요. 만약 이벤트가 부모 컴포넌트에 도달하지 못하게 막고 싶다면 아래의 Button
컴포넌트가 한 것처럼 e.stopPropagation()
을 호출하세요.
버튼을 누르면,
- 리액트는
<button>
에 전달된onClick
핸들러를 호출해요. Button
안에서 정의된 이 핸들러는 다음을 진행해요.e.stopPropagation()
을 호출하여 이벤트가 그 이상 버블링되는 것을 막아요.Toolbar
컴포넌트에서 전달된 prop인onClick
함수를 실행해요.
Toolbar
컴포넌트 안에서 정의된 함수는 버튼의 alert를 보여줘요.- 전파가 중단되었기 때문에 부모
<div>
의onClick
핸들러는 실행되지 않아요.
e.stopPropagation()
의 결과로 버튼을 클릭하는 것은 이제 (<button>
과 부모 툴바 <div>
에서 온) 두 개의 alert가 아니라 (<button>
에서 온) 하나의 alert만 보여줘요. 버튼을 클릭하는 것은 주변 툴바를 누르는 것과는 다르기 때문에 전파를 막는 것이 이 UI에는 더 적절해요.
캡쳐 단계 이벤트
드물게, 설령 전파를 중단했더라도 자식 컴포넌트의 모든 이벤트를 캐치해야할 때가 있어요. 예를 들어, 분석을 하기 위해 전파 로직과는 상관 없이 모든 클릭 로그가 필요해요. Capture
를 이벤트 이름 마지막에 넣어서 이를 수행할 수 있어요.
<div onClickCapture={() => { /* this runs first */ }}>
<button onClick={e => e.stopPropagation()} />
<button onClick={e => e.stopPropagation()} />
</div>
이벤트는 세 단계로 전파해요.
- 이벤트가 아래로 내려가면서 모든
onClickCapture
핸들러를 호출해요. - 클릭한 엘리먼트의
onClick
핸들러를 실행해요. - 이벤트가 위로 올라가면서
onClick
핸들러를 호출해요.
캡쳐 이벤트는 라우터나 분석과 같은 코드에 유용하지만 아마도 어플리케이션 코드에서는 사용하지 않을 거예요.
Passing handlers as alternative to propagation | 전파를 대신하여 핸들러 전달하기
이 클릭 이벤트가 어떻게 코드를 실행하고 그리고 나서 부모에게서 전달받은 onClick
prop을 호출하는지에 주목하세요.
function Button({ onClick, children }) {
return (
<button onClick={e => {
e.stopPropagation();
onClick();
}}>
{children}
</button>
);
}
부모의 onClick
이벤트 핸들러를 호출하기 전에 이 핸들러를 코드에 추가할 수도 있어요. 이 패턴은 전파를 대체할 수 있어요. 부모 컴포넌트가 몇몇 추가적인 행동을 특정하는 동안 자식 컴포넌트는 이벤트를 처리해줘요. 전파와 달리 이것은 자동적이진 않아요. 하지만 이 패턴의 이점은 여러분이 명확하게 이벤트의 결과로 실행되는 모든 코드를 따라갈 수 있다는 점이에요.
만약 전파에 의존하고 있어 어떤 핸들러가 실행되고 왜 실행되는지를 추적하기 어렵다면 이 방법을 한번 사용해보세요.
Preventing default behavior | 기본 동작 막기
어떤 브라우저 이벤트는 해당 이벤트와 연관된 기본 동작을 수행해요. 이를 테면 <form>
내부의 버튼을 클릭할 때 발생하는 <form>
의 제출(submit) 이벤트는 기본적으로 전체 페이지를 재로드해요.
이 일이 발생하지 않도록 e.preventDefualt()
를 이벤트 객체에서 호출할 수 있어요.
e.stopPropagation()
과 e.preventDefault()
를 혼돈하면 안돼요. 이 둘 모두 이용하지만 관련은 없어요.
e.stopPropagtion()
은 실행되는 태그의 상위에 있는 태그와 연결된 이벤트 핸들러를 중단해요.e.preventDefault()
는 일부 이벤트에 대한 브라우저의 기본적인 동작을 막아요.
Can event handlers have side effects | 이벤트 핸들러에 사이드 이펙트가 있나요?
물론이에요! 이벤트 핸들러는 사이드 이펙트가 발생하기에 최적의 환경이에요.
렌더링 함수와는 달리 이벤트 핸들러는 순수할 필요가 없어요. 그래서 타이핑에 따라 입력값을 바꾸거나 버튼을 누르면 리스트가 변경되는 것과 같이, 무언가를 바꾸기 좋은 장소에요. 그러나 정보를 바꾸기 위해서는 먼저 정보를 저장하는 방법이 필요해요. 리액트에서 이와 같은 작업은 컴포넌트의 메모리인 상태를 사용하여 할 수 있어요. 다음 페이지에서 이에 대해서 배울 거예요.
Recap | 요약
- 함수를
<button>
과 같은 엘리먼트에 prop으로 전달하여 이벤트를 처리할 수 있어요. - 이벤트 핸들러는 호출되는 것이 아니라 전달되어야해요!
onClick={handleClick()}
이 아니라onClick={handleClick}
으로 작성하세요. - 이벤트 핸들러 함수는 분리하거나 인라인에서 작성할 수 있어요.
- 직접 만든 이벤트 핸들러 prop은 어플리케이션에 맞는 이름으로 정의할 수 있어요.
- 이벤트는 위로 전파돼요.
e.stopPropagtion()
을 첫 인자에서 호출하여 전파를 방지하세요. - 이벤트는 원치 않는 브라우저의 기본 동작을 할 수 있어요.
e.preventDefault()
로 기본 동작을 막으세요. - 명시적으로 자식 컴포넌트에서 이벤트 핸들러 prop을 호출하는 것은 전파의 가장 좋은 대안이에요.
Challenges | 도전 과제
1. 이벤트 핸들러 고치기
이 버튼을 클릭하면 페이지의 배경색은 하얀색이나 검은색으로 전환되어야해요. 그러나 여러분이 클릭하면 아무 일도 일어나지 않아요. 이 문제를 해결하세요. (handleClick
내부 로직은 걱정하지 마세요. 그 로직은 문제가 없어요.)
<button onClick={handleClick()}>
가 렌더링하는 동안 handleClick
함수를 전달하는 것이 아니라 호출하고 있어서 발생한 문제에요. ()
호출을 제거하여 <button onClick={handleClick}>
로 작성하면 이슈를 해결할 수 있어요.
또는 <button onClick={() => handleClick()}>
과 같은 또 다른 함수로 호출을 감싸는 방법도 있어요.
2. 이벤트 연결하기
이 ColorSwitch
컴포넌트는 버튼을 렌더링해요. 이 컴포넌트는 페이지의 색상을 변경할 거예요. 버튼을 클릭하면 색상이 변하도록 부모에게서 받은 이벤트 핸들러 prop인 onChangeColor
과 컴포넌트를 연결하세요.
이렇게 한 후, 버튼을 클릭하면 페이지 클릭 카운터가 증가하는 것을 알아차릴 거예요. 부모 컴포넌트를 작성한 동료는 onChangeColor
는 어떤 카운터도 증가시키지 않는다고 주장해요. 무슨 일이 벌어진 걸까요? 버튼을 클릭하면 색상만 변경되고 카운터를 증가시키지 않도록 코드를 고치세요.
먼저 <button onClick={onChangeColor}>
과 같이 이벤트 핸들러를 추가하세요.
그러나 이는 카운터가 증가하는 문제를 발생시켜요. 동료가 주장한 것처럼 onChangeColor
이 이 동작을 안한다면, 문제는 이 이벤트가 위로 전파되고 어떤 핸들러가 그 위에 있는 것이 문제에요. 이 문제를 해결하려면 전파를 막아야해요. 그러나 여전히 onChangeColor
를 호출해야한다는 사실을 잊지 마세요.
'리액트 공식문서 | React Docs > Learn > Learn React' 카테고리의 다른 글
[Adding Interactivity] Render and Commit | 렌더링과 커밋 (2) | 2024.02.18 |
---|---|
[Adding Interactivity] State: A Component's Memory | 상태: 컴포넌트의 메모리 (2) | 2024.02.16 |
[Adding Interactivity] Adding Interactivity Overview | 상호작용 추가하기 개요 (0) | 2024.02.15 |
[Describing the UI] Understanding Your UI as a Tree | 트리로 UI 이해하기 (1) | 2024.02.15 |
[Describing the UI] Keeping Components Pure | 컴포넌트를 순수하게 유지하기 (0) | 2024.02.14 |