학습 자료

이벤트 처리 — 클릭과 입력을 받는다


Article

버튼을 누르고 글자를 입력한다. 사용자와 대화하는 방법이다.

순수 자바스크립트에서는 addEventListener를 썼다. React에서는 JSX 속성으로 붙인다.

함수를 넘긴다

function Button() {
  function handleClick() {
    console.log("눌렸다");
  }

  return <button onClick={handleClick}>클릭</button>;
}

3강에서 본 대로 속성은 카멜 케이스다. onclick이 아니라 onClick이다.

그리고 5강에서 강조한 규칙이 여기서 결정적이다.

괄호 하나 차이

onClick={handleClick}

함수 자체를 넘긴다

  • React 가 클릭할 때 부른다
  • 지금은 실행되지 않는다
  • 올바른 방법

onClick={handleClick()}

실행 결과를 넘긴다

  • 렌더링하는 순간 실행된다
  • 반환값(대개 undefined)이 넘어간다
  • 클릭해도 아무 일 없다

자바스크립트 편 11강의 콜백에서 본 그 규칙이다. 넘길 때는 괄호를 붙이지 않는다.

인자를 넘기려면 감싼다

그런데 인자를 넘겨야 할 때가 있다.

<button onClick={handleDelete(item.id)}>삭제</button>

이러면 렌더링하자마자 삭제된다. 화살표 함수로 감싼다.

<button onClick={() => handleDelete(item.id)}>삭제</button>

이제 넘어가는 건 () => handleDelete(item.id)라는 새 함수다. 클릭하면 그 함수가 실행되고, 그 안에서 handleDelete가 인자와 함께 불린다.

화살표 함수로 감싸면
  1. () => handleDelete(3)이 함수가 넘어간다
  2. 클릭React 가 부른다
  3. handleDelete(3)이제 실행된다

이벤트 객체

핸들러는 첫 인자로 이벤트 객체를 받는다.

function Input() {
  function handleChange(event) {
    console.log(event.target.value);
  }

  return <input onChange={handleChange} />;
}

event.target은 이벤트가 일어난 DOM 요소, event.target.value는 입력창의 값이다.

자주 쓰는 것무엇인가
event.target이벤트가 일어난 요소
event.target.value입력창의 값
event.target.checked체크박스의 상태
event.preventDefault()기본 동작을 막는다
event.stopPropagation()위로 퍼지는 걸 막는다

화살표 함수로 감쌌다면 이벤트를 직접 받아 넘겨야 한다.

<input onChange={e => handleChange(e, item.id)} />

기본 동작을 막는다

<form>을 제출하면 브라우저가 페이지를 새로고침한다. 옛날 방식이다.

function SearchForm() {
  function handleSubmit(event) {
    event.preventDefault();
    console.log("검색한다");
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="q" />
      <button type="submit">검색</button>
    </form>
  );
}

preventDefault()를 빠뜨리면 화면이 깜빡이며 새로고침되고 state가 전부 날아간다. 폼을 다룰 때 가장 먼저 쓰는 줄이다.

이벤트는 위로 퍼진다

자식에서 일어난 이벤트는 부모로 올라간다. 버블링이다.

function Modal({ onClose }) {
  return (
    <div className="overlay" onClick={onClose}>
      <div className="modal">
        <p>내용</p>
      </div>
    </div>
  );
}

배경을 누르면 닫히게 만들었다. 그런데 모달 안을 눌러도 닫힌다. 클릭이 modal에서 overlay로 올라가기 때문이다.

클릭이 올라가는 길
  • overlayonClick={onClose} — 여기까지 올라온다
    • modal핸들러 없음 — 그냥 통과
      • p실제로 클릭된 곳

막으려면 stopPropagation()을 부른다.

<div className="modal" onClick={e => e.stopPropagation()}>

6강에서 Modal을 만들 때 이 줄을 슬쩍 써뒀다. 이제 이유를 안다.

두 가지 막기

preventDefault()

브라우저 기본 동작을 막는다

  • 폼 제출 → 새로고침
  • 링크 클릭 → 페이지 이동
  • 이벤트는 계속 위로 퍼진다

stopPropagation()

위로 퍼지는 걸 막는다

  • 부모의 핸들러가 안 불린다
  • 기본 동작은 그대로 일어난다
  • 남용하면 추적이 어려워진다

제어 컴포넌트 — 입력값도 state 다

입력창의 값을 React가 관리하게 한다.

function SearchForm() {
  const [query, setQuery] = useState("");

  return (
    <form>
      <input value={query} onChange={e => setQuery(e.target.value)} />
      <p>{query.length}자</p>
    </form>
  );
}

두 가지가 짝을 이룬다.

값이 한 바퀴 돈다
  1. statequery = "react"
  2. value={query}화면에 그린다
  3. 타이핑onChange 발생
  4. setQuerystate 를 바꾼다

화면에 보이는 값은 언제나 state다. 1강의 UI = f(state)가 입력창에도 그대로 적용된다.

이렇게 하면 글자 수를 세거나, 대문자로 강제하거나, 특정 문자를 막는 일이 쉬워진다.

<input value={query} onChange={e => setQuery(e.target.value.toUpperCase())} />

체크박스는 value 대신 checked를 쓴다.

const [agreed, setAgreed] = useState(false);

<input
  type="checkbox"
  checked={agreed}
  onChange={e => setAgreed(e.target.checked)}
/>;

필드가 여러 개일 때

폼 필드마다 state와 핸들러를 만들면 지겹다. name 속성을 활용한다.

function Form() {
  const [form, setForm] = useState({ name: "", email: "" });

  function handleChange(e) {
    const { name, value } = e.target;
    setForm(prev => ({ ...prev, [name]: value }));
  }

  return (
    <form>
      <input name="name" value={form.name} onChange={handleChange} />
      <input name="email" value={form.email} onChange={handleChange} />
    </form>
  );
}

[name]: value가 핵심이다. 자바스크립트 편 17강의 대괄호 표기법으로, 변수에 담긴 이름을 키로 쓴다. 핸들러 하나가 모든 필드를 처리한다.

스프레드를 잊으면 다른 필드가 사라진다는 것도 9강에서 봤다.

정리하면

  • 핸들러는 함수 자체를 넘긴다. 괄호를 붙이면 렌더링할 때 실행된다.
  • 인자를 넘기려면 화살표 함수로 감싼다. onClick={() => remove(id)}
  • 핸들러는 이벤트 객체를 받는다. e.target.value로 입력값을 꺼낸다.
  • 폼 제출에는 preventDefault(). <form> 안 버튼의 기본 typesubmit이다.
  • 이벤트는 위로 퍼진다. 막으려면 stopPropagation().
  • 입력값도 state로 관리한다. valueonChange는 항상 짝이다.
  • 필드가 많으면 name 속성과 대괄호 표기법으로 핸들러 하나에 모은다.

Part 3의 절반이 지났다. 다음 강의에서는 배열을 화면에 늘어놓는 법과, 거기서 반드시 필요한 key를 다룬다.

이벤트 처리 — 클릭과 입력을 받는다 — 디코드랩(DCODELAB)