ReactNextCentral

이벤트에 응답하기

Published on
이벤트 핸들러를 사용하여 리액트에서 사용자 상호작용에 대한 응답을 정의하고, 이벤트 핸들러를 컴포넌트에 전달하고 관련된 주의사항과 전파에 대한 이해를 제공하는 가이드입니다.
Table of Contents

리액트는 JSX에 이벤트 핸들러를 추가할 수 있게 해줍니다. 이벤트 핸들러는 클릭, 호버링, 폼 입력에 대한 응답과 같은 사용자 상호작용에 따라 트리거되는 사용자 정의 함수입니다.

이벤트 핸들러 추가하기

이벤트 핸들러를 추가하기 위해 먼저 함수를 정의하고 해당 함수를 적절한 JSX 태그에 속성(props)으로 전달해야 합니다. 예를 들어, 아직 아무런 동작을 하지 않는 버튼을 생각해보겠습니다.

Edit mutable-night-iy9k29

버튼을 클릭할 때 메시지를 표시하도록 만들기 위해 다음 세 가지 단계를 따릅니다.

  1. Button 컴포넌트 내에 handleClick이라는 함수를 선언합니다.
  2. 해당 함수 내에 로직을 구현합니다 (메시지를 표시하기 위해 alert를 사용합니다).
  3. <button> JSX에 onClick={handleClick}를 추가합니다.

Edit holy-tree-kkj3dr

handleClick 함수를 정의하고 <button>속성(props)으로 전달했습니다. 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 {} 내의 JavaScript가 즉시 실행되기 때문입니다.

인라인으로 작성한 코드의 경우에도 동일한 함정이 다른 형태로 나타납니다:

함수 전달 (올바른 방법)함수 호출 (잘못된 방법)
<button onClick={() => alert('...')}><button onClick={alert('...')}>

이와 같이 인라인 코드를 전달하면 클릭 시 실행되지 않고, 컴포넌트가 렌더링될 때마다 실행됩니다.

// 이 경고는 버튼을 클릭하는 대신 컴포넌트가 렌더링될 때마다 실행됩니다!
<button onClick={alert('You clicked me!')}>

인라인으로 이벤트 핸들러를 정의하는 경우 다음과 같이 익명 함수로 감싸야 합니다.

<button onClick={() => alert('You clicked me!')}>

이를테면 이렇게 작성된 코드는 함수를 전달하려는 것입니다.

  • <button onClick={handleClick}>handleClick 함수를 전달합니다.
  • <button onClick={() => alert('...')}>() => alert('...') 함수를 전달합니다.

화살표 함수에 대해 자세히 알아보세요.

이벤트 핸들러에서 props 읽기

이벤트 핸들러는 컴포넌트 내에서 정의되므로 컴포넌트의 props에 접근할 수 있습니다. 다음은 클릭 시 메시지 prop을 표시하는 버튼입니다.

Edit fast-cdn-rkshhm

이렇게 하면 두 개의 버튼이 다른 메시지를 표시할 수 있습니다. 메시지를 변경해 보세요.

이벤트 핸들러를 속성으로 전달하기

자주 사용되는 시나리오 중 하나는 상위 컴포넌트에서 자식의 이벤트 핸들러를 지정하는 것입니다. 예를 들어 버튼을 고려해 보겠습니다. Button 컴포넌트를 사용하는 위치에 따라 다른 기능을 실행할 수 있습니다. 예를 들어 한 컴포넌트에서는 영화 재생을 실행하고 다른 컴포넌트에서는 이미지를 업로드할 수 있습니다.

이를 위해 다음과 같이 이벤트 핸들러로 전달되는 속성을 사용합니다.

Edit jovial-frost-y81hok

여기에서 Toolbar 컴포넌트는 PlayButton과 UploadButton을 렌더링합니다.

  • PlayButton은 내부의 Button에 onClick prop으로 handlePlayClick을 전달합니다.
  • UploadButton은 내부의 Button에 onClick prop으로 () => alert('Uploading!')을 전달합니다.

마지막으로 Button 컴포넌트는 onClick이라는 prop을 허용합니다. 이 prop을 바로 내장된 브라우저의 <button>onClick={onClick}로 전달합니다. 이렇게 하면 리액트가 클릭 시 전달된 함수를 호출합니다.

디자인 시스템을 사용하는 경우 버튼과 같은 컴포넌트에는 스타일링만 포함되고 동작은 지정되지 않는 것이 일반적입니다. 대신 PlayButton과 UploadButton과 같은 컴포넌트에서 이벤트 핸들러를 전달합니다.

이벤트 핸들러 속성의 이름 정하기

<button><div>와 같은 내장된 컴포넌트는 onClick과 같은 브라우저 이벤트 이름만 지원합니다. 그러나 직접 만드는 컴포넌트에서는 이벤트 핸들러 속성의 이름을 원하는 대로 지정할 수 있습니다.

관례적으로 이벤트 핸들러 속성은 on으로 시작하고 대문자로 시작하는 형식으로 작성해야 합니다.

예를 들어 Button 컴포넌트의 onClick prop은 onSmash로 지정할 수 있습니다.

Edit trusting-villani-hwd014

이 예제에서 <button onClick={onSmash}>는 브라우저의 <button> (소문자)에 여전히 onClick이라는 prop이 필요하지만, 사용자 정의 Button 컴포넌트에서 받는 prop 이름은 개발자의 선택에 따라 달라집니다.

컴포넌트가 여러 상호작용을 지원하는 경우 앱 특정 개념에 대한 이벤트 핸들러 속성의 이름을 지정할 수 있습니다. 예를 들어 Toolbar 컴포넌트는 onPlayMovieonUploadImage 이벤트 핸들러를 받습니다.

Edit cool-wildflower-h1031c

App 컴포넌트는 Toolbar가 onPlayMovie 또는 onUploadImage를 어떻게 사용하는지 알 필요가 없습니다. 이것은 Toolbar의 구현 세부사항입니다. 여기에서 Toolbar는 이를 onClick 핸들러로 Button에 전달하지만, 나중에 키보드 단축키에서도 실행할 수 있습니다. onPlayMovie과 같은 앱 특정 상호작용에 대한 props 이름을 사용하면 나중에 사용 방식을 변경할 수 있는 유연성을 제공합니다.

참고 이벤트 핸들러에 적합한 HTML 태그를 사용하는지 확인하세요. 예를 들어, 클릭을 처리하려면 <button onClick={handleClick}>와 같이 <div onClick={handleClick}> 대신 실제 브라우저 <button>을 사용해야 합니다. 실제 브라우저 <button>을 사용하면 키보드 탐색과 같은 내장된 브라우저 동작이 가능해집니다. 버튼의 기본 브라우저 스타일을 좋아하지 않고 링크나 다른 UI 요소와 유사한 모습으로 변경하려면 CSS를 사용하여 구현할 수 있습니다. 접근성 있는 마크업 작성에 대해 자세히 알아보세요.

이벤트 전파

이벤트 핸들러는 해당 컴포넌트의 자식 요소에서도 이벤트를 캐치합니다. 이벤트가 "버블링" 또는 "전파"되어 트리 상위로 이동한다고 말합니다. 이 <div>는 두 개의 버튼을 포함하고 있습니다. <div>와 각 버튼에는 자체적인 onClick 핸들러가 있습니다. 버튼을 클릭하면 어떤 핸들러가 실행될 것 같나요?

Edit intelligent-rui-u3njul

버튼을 클릭하면 해당 버튼의 onClick이 먼저 실행되고 그 다음에 부모 <div>의 onClick이 실행됩니다. 따라서 두 개의 메시지가 표시됩니다. 툴바 자체를 클릭하면 부모 <div>의 onClick만 실행됩니다.

주의사항

모든 이벤트는 리액트에서 전파되며, 그 중에서도 onScroll은 연결된 JSX 태그에서만 작동합니다.

전파 중지

이벤트 핸들러는 이벤트 객체를 유일한 인수로 받습니다. 관례적으로 이벤트 객체의 변수명은 e로 사용됩니다. 이 객체를 사용하여 이벤트에 대한 정보를 읽을 수 있습니다.

이벤트 전파를 중지할 수도 있습니다. 이벤트가 상위 컴포넌트로 전달되지 않도록 하려면 e.stopPropagation()을 호출해야 합니다. Button 컴포넌트에서 이렇게 하고 있습니다.

Edit jolly-hooks-xeej9l

버튼을 클릭하면 다음과 같은 작업을 수행합니다.

  1. 리액트는 <button>에 전달된 onClick 핸들러를 호출합니다.
  2. Button에서 정의된 이 핸들러는 다음 작업을 수행합니다.
    • e.stopPropagation()을 호출하여 이벤트가 더 이상 전파되지 않도록 합니다.
    • onClick 함수를 호출합니다. 이 함수는 Toolbar 컴포넌트에서 전달된 prop입니다.
  3. Toolbar 컴포넌트에서 정의된 이 함수는 버튼의 알림을 표시합니다.
  4. 전파가 중지되었기 때문에 부모 <div>의 onClick 핸들러는 실행되지 않습니다.

e.stopPropagation()을 통해 버튼을 클릭하면 두 개의 알림 대신 하나의 알림만 표시됩니다. 이는 버튼을 클릭하는 것과 툴바 자체를 클릭하는 것은 다른 동작이기 때문에 전파를 중지하는 것이 이 UI에 맞습니다.

자세히 알아보기: 캡처 단계 이벤트

드물게, 전파가 중지된 이벤트에 대해서도 모든 자식 요소의 이벤트를 캐치해야 할 수 있습니다. 예를 들어 전파 논리와 상관없이 모든 클릭을 분석에 기록하려는 경우입니다. 다음과 같이 이벤트 이름 끝에 Capture를 추가하여 이를 수행할 수 있습니다.

<div onClickCapture={() => { /* 이것이 가장 먼저 실행됩니다 */ }}>
  <button onClick={e => e.stopPropagation()} />
  <button onClick={e => e.stopPropagation()} />
</div>

각 이벤트는 세 단계로 전파됩니다.

  1. 모든 onClickCapture 핸들러를 호출하면서 아래로 이동합니다.
  2. 클릭한 요소의 onClick 핸들러를 실행합니다.
  3. 위로 이동하면서 모든 onClick 핸들러를 호출합니다.

Capture 이벤트는 라우터나 분석과 같은 코드에 유용하지만, 앱 코드에서는 사용하지 않을 것입니다.

전파 대신 핸들러 전달하기

다음과 같이 이 클릭 핸들러는 코드 한 줄을 실행하고 그 다음에 부모로부터 전달받은 onClick prop을 호출합니다.

function Button({ onClick, children }) {
  return (
    <button onClick={e => {
      e.stopPropagation();
      onClick();
    }}>
      {children}
    </button>
  );
}

onClick 이벤트 핸들러 전에 더 많은 코드를 추가하여도 됩니다. 이 패턴은 전파 대신에 대체 수단을 제공합니다. 자식 컴포넌트에서 이벤트를 처리하면서 동시에 부모 컴포넌트에서 일부 추가 동작을 지정할 수 있습니다. 전파와 달리 이 방식은 자동적이지 않습니다. 하지만 이 패턴의 장점은 어떤 이벤트로 인해 일어나는 전체 코드 체인을 명확하게 따를 수 있다는 점입니다.

전파에 의존하고 있고 어떤 핸들러가 실행되는지 추적하기 어려운 경우에는 이 접근 방식을 대신 사용해 보세요.

기본 동작 방지

일부 브라우저 이벤트에는 기본 동작이 연결되어 있습니다. 예를 들어 <form> 내에서 버튼을 클릭할 때 발생하는 제출(submit) 이벤트는 기본적으로 전체 페이지를 다시 로드합니다.

Edit still-hill-uqe5bm

이를 방지하기 위해 이벤트 객체에서 e.preventDefault()를 호출할 수 있습니다.

Edit zen-mccarthy-g080oe

e.stopPropagation()e.preventDefault()을 혼동하지 마세요. 두 가지 모두 유용하지만 관련성이 없습니다.

  • e.stopPropagation()은 상위 태그에 연결된 이벤트 핸들러의 실행을 막습니다.
  • e.preventDefault()은 일부 이벤트에 대한 브라우저의 기본 동작을 막습니다.

이벤트 핸들러에서 Side-effect이 가능한가요?

절대로 가능합니다! 이벤트 핸들러는 Side-effect을 위한 최적의 장소입니다.

렌더링 함수와는 달리 이벤트 핸들러는 순수함이 될 필요가 없으므로 입력 타이핑에 따라 입력의 값 변경이나 버튼 누름에 따라 목록 변경과 같은 작업을 수행하는 데 좋은 장소입니다. 그러나 정보를 변경하기 위해서는 먼저 정보를 저장하는 방법이 필요합니다. 리액트에서는 이를 상태(state), 컴포넌트의 메모리를 사용하여 수행합니다. 이에 대해 다음 페이지에서 자세히 알아보게 됩니다.

요약

  • <button>과 같은 요소에 함수를 prop으로 전달하여 이벤트를 처리할 수 있습니다.
  • 이벤트 핸들러는 호출하지 않고 전달해야 합니다! onClick={handleClick}이 아닌 onClick={handleClick()}입니다.
  • 이벤트 핸들러 함수를 별도로 정의하거나 JSX 내에서 인라인으로 정의할 수 있습니다.
  • 이벤트 핸들러는 컴포넌트 내에서 정의되므로 props에 액세스할 수 있습니다.
  • 부모에서 정의된 이벤트 핸들러를 자식에게 전달하는 prop으로 전달할 수 있습니다.
  • 앱별 개념을 위해 사용자 정의 이벤트 핸들러 prop의 이름을 지정할 수 있습니다.
  • 이벤트는 상위로 전파합니다. 이를 막으려면 첫 번째 인수에 e.stopPropagation()을 호출하세요.
  • 원하지 않는 기본 브라우저 동작이 있는 이벤트가 있을 수 있습니다. 이를 방지하려면 e.preventDefault()를 호출하세요.
  • 자식 핸들러에서 부모 핸들러의 prop을 명시적으로 호출하는 것은 전파에 대한 대체 수단입니다.