SPACE RUMI

Hi, I am rumi. Let's Splattack!

[STUDY] 스터디/React Docs

React docs beta 한글 번역 : Responding to Events - 이벤트에 응답하기

백루미 2023. 2. 22. 01:45
반응형

상호작용 추가 : 이벤트에 응답하기

React를 사용하면 JSX에 이벤트 핸들러를 추가할 수 있습니다. 이벤트 핸들러는 클릭, 마우스오버, 포커스 양식 입력 등과 같은 상호작용에 반응하여 트리거되는 자체 함수입니다.

 

학습 내용

  • 이벤트 핸들러를 작성하는 다양한 방법
  • 부모 컴포넌트에서 이벤트 처리 로직을 전달하는 방법
  • 이벤트가 전파되는 방식과 중지하는 방법

 

이벤트 핸들러 추가하기 

이벤트 핸들러를 추가하려면 먼저 함수를 정의한 다음, 이를 적절한 JSX 태그에 props로 전달합니다.
예를 들어, 여기에는 아직 아무 작업도 수행하지 않는 버튼이 있습니다:

export default function Button() {
  return (
    <button>
      I don't do anything
    </button>
  );
}

다음 세 단계에 따라 사용자가 클릭할 때 메시지를 표시하도록 설정할 수 있습니다:

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

export default function Button() {
  function handleClick() {
    alert('You clicked me!');
  }

  return (
    <button onClick={handleClick}>
      Click me
    </button>
  );
}

 

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 이벤트 핸들러로 전달됩니다. 
이렇게 하면 React가 이를 기억하고 사용자가 버튼을 클릭할 때만 함수를 호출하도록 지시합니다.

두 번째 예제에서 handleClick() 끝에 있는 ()은 클릭 없이 렌더링 중에 즉시 함수를 실행합니다.
이는 JSX의 { } 내부의 자바스크립트가 바로 실행되기 때문입니다.

인라인으로 코드를 작성할 때는 동일한 함정이 다른 방식으로 나타납니다:

함수 전달(정답) : <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에 액세스할 수 있습니다. 다음은 클릭하면 message prop과 함께 알림을 표시하는 버튼입니다:

function AlertButton({ message, children }) {
  return (
    <button onClick={() => alert(message)}>
      {children}
    </button>
  );
}

export default function Toolbar() {
  return (
    <div>
      <AlertButton message="Playing!">
        Play Movie
      </AlertButton>
      <AlertButton message="Uploading!">
        Upload Image
      </AlertButton>
    </div>
  );
}

이렇게 하면 이 두 버튼이 서로 다른 메시지를 표시할 수 있습니다. 전달되는 메시지를 변경해 보세요.

이벤트 핸들러를 Props로 전달하기 

부모 컴포넌트가 자식의 이벤트 핸들러를 지정하는 경우가 종종 있습니다. 버튼 컴포넌트를 사용하는 위치에 따라 버튼은 동영상을 재생하고 다른 버튼은 이미지를 업로드하는 등 서로 다른 기능을 실행하고 싶을 수 있습니다.

이렇게 하려면 다음과 같이 컴포넌트가 부모로부터 받은 props 이벤트 핸들러로 전달하면 됩니다:

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

function PlayButton({ movieName }) {
  function handlePlayClick() {
    alert(`Playing ${movieName}!`);
  }

  return (
    <Button onClick={handlePlayClick}>
      Play "{movieName}"
    </Button>
  );
}

function UploadButton() {
  return (
    <Button onClick={() => alert('Uploading!')}>
      Upload Image
    </Button>
  );
}

export default function Toolbar() {
  return (
    <div>
      <PlayButton movieName="Kiki's Delivery Service" />
      <UploadButton />
    </div>
  );
}

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

  • PlayButton은 handlePlayClick을 onClick props로 Button의 내부에 전달합니다.
  • UPloadButton은 () => alert('업로드 중!')를 onClick 프로퍼티로 내부의 버튼에 전달합니다.

마지막으로 버튼 컴포넌트는 onClick이라는 props를 받습니다.
이 컴포넌트는 onClick={onClick}을 사용하여 해당 프로퍼티를 내장 브라우저 <button>에 직접 전달합니다.
그러면 React가 클릭 시 전달된 함수를 호출하도록 지시합니다.

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

 

이벤트 핸들러 props 이름 지정

<button> 및 <div>와 같은 기본 제공 컴포넌트는 onClick과 같은 브라우저 이벤트 이름만 지원합니다. 하지만 자체 컴포넌트를 빌드할 때는 이벤트 핸들러 props 이름을 원하는 방식으로 지정할 수 있습니다.

관례에 따라 이벤트 핸들러 props는, on으로 시작하고 그 뒤에 대문자가 와야 합니다.
예를 들어 버튼 컴포넌트의 onClick props는 onSmash로 호출할 수 있습니다:

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 onClick={onSmash}>는 브라우저 <button>(소문자)에 여전히 onClick이라는 prop이 필요함을 보여주지만, 사용자 정의 버튼 컴포넌트가 수신하는 prop 이름은 사용자가 결정할 수 있습니다!


컴포넌트가 여러 상호작용을 지원하는 경우, 이벤트 핸들러 props 이름을 앱별 개념에 맞게 지정할 수 있습니다.
예를 들어, 이 Toolbar 컴포넌트는 onPlayMovie 및 onUploadImage 이벤트 핸들러를 수신합니다:

export default function App() {
  return (
    <Toolbar
      onPlayMovie={() => alert('Playing!')}
      onUploadImage={() => alert('Uploading!')}
    />
  );
}

function Toolbar({ onPlayMovie, onUploadImage }) {
  return (
    <div>
      <Button onClick={onPlayMovie}>
        Play Movie
      </Button>
      <Button onClick={onUploadImage}>
        Upload Image
      </Button>
    </div>
  );
}

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

App 컴포넌트는 Toolbar의 onPlayMovie 또는 onUploadImage로 어떤 작업을 수행할지 알 필요가 없다는 점을 주목하세요. 이것이 Toolbar의 구현 세부 사항입니다. 여기서 Toolbar 버튼에 onClick 핸들러로 전달하지만 나중에 키보드 단축키에서 트리거할 수도 있습니다. props 이름을 onPlayMovie와 같은 앱별 상호작용의 이름을 따서 지정하면 나중에 props의 사용 방식을 유연하게 변경할 수 있습니다.

 

이벤트 전파 

이벤트 핸들러는 컴포넌트에 있을 수 있는 모든 자식으로부터 이벤트를 포착합니다.
이벤트가 트리 위로 '버블' 또는 '전파'되는 것을 이벤트가 발생한 곳에서 시작하여 트리 위로 올라간다고 말합니다.

이 <div> 는 두개의 버튼을 가지고 있습니다.  <div>와 각 버튼에는 모두 고유한 onClick 핸들러가 있습니다.
버튼을 클릭할 때 어떤 핸들러가 실행될까요?

export default function Toolbar() {
  return (
    <div className="Toolbar" onClick={() => {
      alert('You clicked on the toolbar!');
    }}>
      <button onClick={() => alert('Playing!')}>
        Play Movie
      </button>
      <button onClick={() => alert('Uploading!')}>
        Upload Image
      </button>
    </div>
  );
}

 

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

 

함정

연결된 JSX 태그에서만 작동하는 onScroll을 제외한 모든 이벤트는, React에서 전파됩니다.

 

전파 중지

이벤트 핸들러는 이벤트 객체를 유일한 인수로 받습니다. 일반적으로 "이벤트"를 의미하는 e라고 합니다. 이 객체를 사용하여 이벤트에 대한 정보를 읽을 수 있습니다.

이 이벤트 객체를 사용하면 전파를 중지할 수도 있습니다. 이벤트가 부모 컴포넌트에 도달하지 못하도록 하려면 버튼 컴포넌트처럼 e.stopPropagation()을 호출해야 합니다:

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>
      <Button onClick={() => alert('Uploading!')}>
        Upload Image
      </Button>
    </div>
  );
}

 

button을 클릭하면:

1. React는 <button>에 전달된 onClick 핸들러를 호출합니다.
2. Button에 정의된 이 핸들러는 다음을 수행합니다.

  • 이벤트가 더 이상 버블링되지 않도록 e.stopPropagation()을 호출합니다.
  • Toolbar 컴포넌트에서 전달된 props인 onClick 함수를 호출합니다.

3. 툴바 구성 요소에 정의된 이 함수는 버튼 자체의 알럿을 표시합니다.
4. 전파가 중지되었으므로 부모 <div>의 onClick 핸들러가 실행되지 않습니다.

e.stopPropagation()의 결과로 이제 버튼을 클릭하면, 두 개의 알림(<button>과 상위 툴바 <div>에서) 이 아닌
하나의 알림(<button>에서)만 표시됩니다.

버튼을 클릭하는 것은 주변 도구 모음을 클릭하는 것과 같지 않으므로 이 UI에서는 전파를 중지하는 것이 합리적입니다.

 

Deep-Dive - 캡쳐 단계 이벤트

드물지만 하위 요소에서 전파가 중지된 경우에도 모든 이벤트를 포착해야 하는 경우가 있습니다. 예를 들어, 전파 로직에 관계없이 모든 클릭을 애널리틱스에 기록하고자 할 수 있습니다. 이벤트 이름 끝에 Capture를 추가하면 이 작업을 수행할 수 있습니다:

<div onClickCapture={() => { /* this runs first */ }}>
  <button onClick={e => e.stopPropagation()} />
  <button onClick={e => e.stopPropagation()} />
</div>

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

  • 아래로 이동하여 모든 onClickCapture 핸들러를 호출합니다.
  • 클릭된 요소의 onClick 핸들러를 실행합니다.
  • 위쪽으로 이동하여 모든 onClick 핸들러를 호출합니다.

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

 

전파의 대안으로 핸들러 전달하기 

이 클릭 핸들러가 코드 한 줄을 실행한 다음 부모가 전달한 onClick prop을 호출하는 방식을 주목하세요:

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

부모 onClick 이벤트 핸들러를 호출하기 전에 이 핸들러에 코드를 더 추가할 수도 있습니다. 이 패턴은 전파에 대한 대안을 제공합니다. 자식 컴포넌트가 이벤트를 처리하는 동시에 부모 컴포넌트가 몇 가지 추가 동작을 지정할 수 있게 해줍니다. propagation과 달리 자동이 아닙니다. 하지만 이 패턴의 장점은 특정 이벤트의 결과로 실행되는 전체 체인 코드를 명확하게 따라갈 수 있다는 것입니다.

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

 

기본 동작 방지 

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

export default function Signup() {
  return (
    <form onSubmit={() => alert('Submitting!')}>
      <input />
      <button>Send</button>
    </form>
  );
}

이벤트 객체에서 e.preventDefault()를 호출하여 이런 일이 발생하지 않도록 할 수 있습니다:

export default function Signup() {
  return (
    <form onSubmit={e => {
      e.preventDefault();
      alert('Submitting!');
    }}>
      <input />
      <button>Send</button>
    </form>
  );
}

e.stopPropagation()과 e.preventDefault()를 혼동하지 마세요. 둘 다 유용하지만 서로 관련이 없습니다:

  • e.stopPropagation()은 위의 태그에 연결된 이벤트 핸들러의 실행을 중지합니다.
  • e.preventDefault()는 해당 이벤트가 있는 몇 가지 이벤트에 대한 기본 브라우저 동작을 방지합니다.

 

이벤트 핸들러에 사이드이펙트(부작용) 가 생길 수 있나요? 

물론입니다! 이벤트 핸들러는 부작용이 발생하기 가장 좋은 곳입니다.

렌더링 함수와 달리 이벤트 핸들러는 순수할 필요가 없으므로 입력에 반응하여 입력값을 변경하거나 버튼 누름에 반응하여 목록을 변경하는 등 무언가를 변경하기에 좋은 곳입니다. 하지만 일부 정보를 변경하려면 먼저 정보를 저장할 방법이 필요합니다. React에서는 컴포넌트의 메모리인 state를 사용해 이 작업을 수행합니다. 다음 페이지에서 이에 대한 모든 것을 배우게 될 것입니다.

 

요약

  • <button>과 같은 요소에 함수를 prop으로 전달하여 이벤트를 처리할 수 있습니다.
  • 이벤트 핸들러는 호출이 아니라 전달되어야 합니다! onClick={handleClick}, onClick={handleClick()}가 아닙니다.
  • 이벤트 핸들러 함수를 개별적으로 또는 인라인으로 정의할 수 있습니다.
  • 이벤트 핸들러는 컴포넌트 내부에 정의되므로 props에 액세스할 수 있습니다.
  • 부모에서 이벤트 핸들러를 선언하고 이를 자식에게 prop으로 전달할 수 있습니다.
  • 애플리케이션별 이름으로 이벤트 핸들러 prop을 직접 정의할 수 있습니다.
  • 이벤트는 위쪽으로 전파됩니다. 이를 방지하려면 첫 번째 인수에 e.stopPropagation()을 호출하세요.
  • 이벤트에 원치 않는 기본 브라우저 동작이 있을 수 있습니다. 이를 방지하려면 e.preventDefault()를 호출하세요
  • 자식 핸들러에서 이벤트 핸들러 props를 명시적으로 호출하는 것은 전파에 대한 좋은 대안입니다.

 

과제 1 / 2: 이벤트 핸들러 수정하기 
이 버튼을 클릭하면 페이지 배경이 흰색과 검은색으로 전환되어야 합니다. 하지만 이 버튼을 클릭해도 아무 일도 일어나지 않습니다. 문제를 수정하세요. (핸들클릭 내부의 로직은 걱정하지 마세요. 그 부분은 괜찮습니다.)

 

과제 2/2: 이벤트 연결하기 
이 ColorSwitch 컴포넌트는 버튼을 렌더링합니다. 페이지 색상을 변경해야 합니다. 버튼을 클릭하면 색상이 변경되도록 부모로부터 수신하는 onChangeColor 이벤트 핸들러 prop에 연결하세요.

이렇게 하면 버튼을 클릭하면 페이지 클릭 카운터도 증가하는 것을 확인할 수 있습니다. 부모 컴포넌트를 작성한 동료는 onChangeColor가 카운터를 증가시키지 않는다고 주장합니다. 또 어떤 일이 벌어지고 있을까요? 버튼을 클릭하면 색상만 변경되고 카운터가 증가하지 않도록 수정하세요.

 

https://codesandbox.io/s/2juca-2caebteo-d4v8e4?file=/src/App.js

 

발표 요약)

1. 이벤트 핸들러는 관례대로 handle + 이벤트이름 으로 함수명을 짓는다.
2. onClick에는 호출이 아니라 함수를 전달해야한다. 인라인으로 정의할때는 익명함수로 래핑해서 전달한다.

3. 사용자정의 이벤트 핸들러 props 명칭은 on으로 시작하고 뒤에 대문자가 온다.

4. 이벤트 버블링은, 이벤트 핸들러가 등록된 요소로부터 ~ window로 올라가면서 이벤트를 다 실행시키는데, e.stopPropagation를 통해 이벤트 전파를 막을 수 있다.

5. 브라우저의 기본 이벤트를 막으려면 e.preventDefault 를 사용한다.

반응형