이벤트 처리 — 클릭과 입력을 받는다
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가 인자와 함께 불린다.
- () => handleDelete(3)이 함수가 넘어간다
- 클릭React 가 부른다
- 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로 올라가기 때문이다.
- overlay
- modal
- p
- modal
막으려면 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>
);
}
두 가지가 짝을 이룬다.
- statequery = "react"
- value={query}화면에 그린다
- 타이핑onChange 발생
- 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>안 버튼의 기본type은submit이다. - 이벤트는 위로 퍼진다. 막으려면
stopPropagation(). - 입력값도 state로 관리한다.
value와onChange는 항상 짝이다. - 필드가 많으면
name속성과 대괄호 표기법으로 핸들러 하나에 모은다.
Part 3의 절반이 지났다. 다음 강의에서는 배열을 화면에 늘어놓는 법과, 거기서 반드시 필요한 key를 다룬다.