이벤트 처리
React 이벤트 핸들러를 연결하고 이벤트 객체와 추가 인자를 전달하며 버블링과 전파 중단을 제어합니다.
지금까지 App.jsx, UserProfile.jsx, ToggleSwitch.jsx 같은 컴포넌트를 만들고, UserProfile에 props로 데이터를 넘기며, ToggleSwitch에서 state로 컴포넌트 안의 데이터를 관리하는 방법을 배웠습니다. 이제 웹 애플리케이션의 핵심인 사용자와의 상호작용을 구현할 차례입니다. 사용자가 버튼을 클릭하거나, 입력 필드에 글자를 입력하거나, 마우스를 움직이는 행위를 이벤트(event)라고 부릅니다. 이 글에서는 React가 이런 이벤트를 어떻게 받아 처리하는지 자세히 알아봅니다.
이벤트에서 다음 렌더까지 — 상태 추적
사용자가 React를 입력하면 target.value, 현재 text snapshot, 다음 state 요청, DOM value/문장이 서로 다른 순간의 값이다. setter 즉시 대입 오해를 줄인다.
EVENT → STATE → UI
이벤트 핸들러가 읽은 입력값을 state와 화면에 연결합니다
InputMirror에서 사용자가 React를 입력하면 handleChange가 값을 읽어 setText를 호출합니다. 다음 렌더는 input의 value와 아래 표시 문장에 같은 text를 사용합니다.
InputMirror의 한 데이터 흐름
onChange={handleChange}
setText(e.target.value);
value={text}
표시: 입력된 내용: React새 입력값 읽기 → setter 요청 → 다음 렌더 → input과 표시 텍스트 반영입니다.
함수 전달과 인자 전달
onClick={handleClickHello}
onClick={() =>
handleDeleteItem(item.id)}함수 자체나 wrapper 함수를 전달합니다. onClick={handleClickHello()}는 렌더 중 즉시 호출되므로 이벤트 시점 실행과 다릅니다.
stopPropagation · 전파 중단
버튼 핸들러가 실행된 뒤 부모 click 핸들러로 이어지는 버블링을 막습니다. 링크 이동 같은 기본 동작까지 자동 차단하지는 않습니다.
preventDefault · 기본 동작 중단
링크 이동·form 제출 같은 기본 동작을 막습니다. 부모의 이벤트 핸들러로 전파되는지 여부는 별도로 결정합니다.
이 예의 조건
- 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
- 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 전체 관계·조건 설명 | 입력 이벤트→handleChange에서 target.value 읽기→setText 요청→다음 렌더의 노드 4개·간선 3개다. 같은 text가 제어 input의 value 및 표시 문장에 쓰이며 현재 핸들러 변수에 즉시 대입하는 경로가 아니다. |
| 전체 관계·조건 설명 | 입력 이벤트→handleChange에서 target.value 읽기→setText 요청→다음 렌더의 노드 4개·간선 3개다. 같은 text가 제어 input의 value 및 표시 문장에 쓰이며 현재 핸들러 변수에 즉시 대입하는 경로가 아니다. 모바일에서도 노드와 연결 방향·독립 경로가 같다. |
| 장면 | 현재 관찰값 | 설명 | 변화 이유 |
|---|---|---|---|
| DOM 입력 | DOM 값: React / handler text: 현재 snapshot | 사용자가 React를 입력하면 event target.value가 React다 | DOM 사건 값과 React state의 순간이 다르다 |
| setText 요청 | 요청: React / 현재 text: 즉시 대입 아님 | handler가 읽은 값을 다음 state로 요청한다 | setter는 실행 중 지역 변수를 다시 쓰지 않는다 |
| 다음 UI | input: React / 표시 문장: React | input value와 문장이 새 text를 읽는다 | 다음 렌더에서 제어 값과 출력이 같은 state를 사용한다 |
이벤트에서 다음 렌더까지 — 상태 추적
사용자가 React를 입력하면 target.value, 현재 text snapshot, 다음 state 요청, DOM value/문장이 서로 다른 순간의 값이다. setter 즉시 대입 오해를 줄인다.
EVENT → STATE → UI
이벤트 핸들러가 읽은 입력값을 state와 화면에 연결합니다
InputMirror에서 사용자가 React를 입력하면 handleChange가 값을 읽어 setText를 호출합니다. 다음 렌더는 input의 value와 아래 표시 문장에 같은 text를 사용합니다.
InputMirror의 한 데이터 흐름
onChange={handleChange}
setText(e.target.value);
value={text}
표시: 입력된 내용: React새 입력값 읽기 → setter 요청 → 다음 렌더 → input과 표시 텍스트 반영입니다.
함수 전달과 인자 전달
onClick={handleClickHello}
onClick={() =>
handleDeleteItem(item.id)}함수 자체나 wrapper 함수를 전달합니다. onClick={handleClickHello()}는 렌더 중 즉시 호출되므로 이벤트 시점 실행과 다릅니다.
stopPropagation · 전파 중단
버튼 핸들러가 실행된 뒤 부모 click 핸들러로 이어지는 버블링을 막습니다. 링크 이동 같은 기본 동작까지 자동 차단하지는 않습니다.
preventDefault · 기본 동작 중단
링크 이동·form 제출 같은 기본 동작을 막습니다. 부모의 이벤트 핸들러로 전파되는지 여부는 별도로 결정합니다.
이 예의 조건
- 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
- 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 전체 관계·조건 설명 | 입력 이벤트→handleChange에서 target.value 읽기→setText 요청→다음 렌더의 노드 4개·간선 3개다. 같은 text가 제어 input의 value 및 표시 문장에 쓰이며 현재 핸들러 변수에 즉시 대입하는 경로가 아니다. |
| 전체 관계·조건 설명 | 입력 이벤트→handleChange에서 target.value 읽기→setText 요청→다음 렌더의 노드 4개·간선 3개다. 같은 text가 제어 input의 value 및 표시 문장에 쓰이며 현재 핸들러 변수에 즉시 대입하는 경로가 아니다. 모바일에서도 노드와 연결 방향·독립 경로가 같다. |
| 장면 | 현재 관찰값 | 설명 | 변화 이유 |
|---|---|---|---|
| DOM 입력 | DOM 값: React / handler text: 현재 snapshot | 사용자가 React를 입력하면 event target.value가 React다 | DOM 사건 값과 React state의 순간이 다르다 |
| setText 요청 | 요청: React / 현재 text: 즉시 대입 아님 | handler가 읽은 값을 다음 state로 요청한다 | setter는 실행 중 지역 변수를 다시 쓰지 않는다 |
| 다음 UI | input: React / 표시 문장: React | input value와 문장이 새 text를 읽는다 | 다음 렌더에서 제어 값과 출력이 같은 state를 사용한다 |
위 그림은 이 글의 InputMirror 예제에서 입력 이벤트가 다음 화면이 되기까지의 네 단계를 보여 줍니다. 사용자가 “React”를 입력하면 입력 이벤트가 일어나고, handleChange가 e.target.value로 새 입력값을 읽어 setText로 다음 상태를 요청하며, 다음 렌더에서 같은 text가 입력칸의 value와 “입력된 내용: React”라는 표시 문장에 함께 쓰입니다. 그림의 설명처럼 이 경로는 현재 처리 함수의 변수에 값을 바로 대입하는 것이 아니라, setter의 요청이 다음 렌더로 이어지는 흐름입니다.
그림의 나머지 칸은 이 글의 뒤에서 다룰 내용을 미리 정리합니다. onClick={handleClickHello}처럼 함수 자체를 넘기거나, onClick={() => handleDeleteItem(item.id)}처럼 인자를 넘기는 감싸는 함수를 넘겨야 하며, onClick={handleClickHello()}라고 쓰면 렌더하는 동안 바로 호출되어 이벤트가 일어날 때 실행되는 것과 달라집니다. 또 stopPropagation()은 버튼의 처리 함수가 실행된 뒤 부모의 클릭 처리 함수로 이어지는 버블링을 막을 뿐 링크 이동 같은 기본 동작은 막지 않고, preventDefault()는 링크 이동이나 폼 제출 같은 기본 동작을 막을 뿐 부모로의 전파 여부는 따로 정합니다.
리액트 이벤트 시스템 개요
이벤트 처리란 이벤트 처리 함수(event handler)로 사용자의 행동을 받아 상태를 바꾸거나 기본 동작을 제어하는 흐름입니다. 본문에서는 줄여서 처리 함수라고도 쓰며, 그림과 코드 주석, 일부 제목의 이벤트 핸들러나 핸들러도 같은 함수를 가리킵니다. React의 이벤트 시스템은 브라우저의 원래(native) 이벤트 시스템과 비슷하지만 몇 가지 중요한 차이가 있습니다.
먼저 처리 함수를 넘기는 방법이 다릅니다. HTML에서는 onclick="myFunction()"처럼 속성에 문자열로 처리 코드를 적지만, React의 JSX에서는 자바스크립트 함수를 중괄호 {} 안에 직접 넘깁니다.
// HTML
<button onclick="alert('클릭!')">클릭</button>
// React (JSX)
<button onClick={() => alert('클릭!')}>클릭</button>이벤트 속성의 이름도 HTML의 소문자와 달리 onClick, onChange, onMouseOver처럼 camelCase로 씁니다. 그리고 React의 처리 함수가 받는 이벤트는 브라우저의 이벤트 객체 그 자체가 아니라, React가 브라우저의 이벤트를 감싸서 제공하는 합성 이벤트(SyntheticEvent) 객체입니다. 합성 이벤트는 모든 브라우저에서 같은 방식으로 동작하도록 표준화되어 있어 브라우저 사이의 차이를 줄여 주며, e.stopPropagation()과 e.preventDefault() 같은 표준 메서드도 그대로 제공합니다.
버튼 클릭 이벤트 처리하기
가장 흔히 쓰는 이벤트인 버튼 클릭을 처리해 봅니다. src/components 폴더 안에 MessageChanger.jsx 파일을 만듭니다.
import { useState } from 'react';
function MessageChanger() {
// (1) state 선언: 초기 메시지는 "환영합니다!"
const [message, setMessage] = useState("환영합니다!");
// (2) 이벤트 핸들러 함수 정의
const handleClickHello = () => {
setMessage("안녕하세요!"); // state 업데이트
};
const handleClickGoodbye = () => {
setMessage("안녕히 가세요!"); // state 업데이트
};
return (
<div style={{ border: '1px dashed #999', padding: '15px', margin: '20px', borderRadius: '8px' }}>
<h2>메시지 변경하기</h2>
<p style={{ fontSize: '20px', fontWeight: 'bold' }}>{message}</p>
<button onClick={handleClickHello} style={{ margin: '5px' }}>
안녕하세요 버튼
</button>
<button onClick={handleClickGoodbye} style={{ margin: '5px' }}>
안녕히 가세요 버튼
</button>
</div>
);
}
export default MessageChanger;이 컴포넌트는 message state를 만들고 초기값을 “환영합니다!”로 정했습니다. handleClickHello와 handleClickGoodbye는 버튼을 클릭할 때 호출될 이벤트 처리 함수입니다. button의 onClick 속성에는 onClick={handleClickHello}처럼 정의한 함수의 이름을 넘겼는데, 함수를 호출하는 형태인 handleClickHello()가 아니라는 점에 주의하세요. 호출하는 형태로 쓰면 컴포넌트가 렌더링될 때마다 함수가 곧바로 실행됩니다. 함수는 이벤트가 일어났을 때 실행되어야 하므로 함수 자체를 넘겨야 합니다.
이제 src/App.jsx에서 MessageChanger 컴포넌트를 불러와 씁니다.
import './App.css';
// ... 다른 컴포넌트 임포트 ...
import MessageChanger from './components/MessageChanger'; // MessageChanger 컴포넌트 불러오기
function App() {
return (
<div className="App">
<h1>나의 React 앱</h1>
{/* ... 다른 컴포넌트 ... */}
<MessageChanger />
</div>
);
}
export default App;브라우저에서는 메시지와 버튼 두 개가 보입니다. 처음에는 “환영합니다!”가 보이고, “안녕하세요 버튼”을 누르면 “안녕하세요!”, “안녕히 가세요 버튼”을 누르면 “안녕히 가세요!”로 바뀝니다. 같은 버튼을 다시 눌러도 같은 메시지로 바꿀 뿐 값이 쌓이지는 않습니다.
이벤트 객체(e) 사용하기
이벤트 처리 함수는 이벤트가 일어났을 때 그 이벤트의 정보를 담은 이벤트 객체를 인자로 받습니다. 이 객체로 이벤트가 일어난 요소, 마우스의 좌표, 키보드 입력 값 같은 여러 정보에 접근할 수 있습니다. 입력 필드의 값을 화면에 그대로 비춰 보는 예제를 만들어 봅니다. src/components 폴더 안에 InputMirror.jsx 파일을 만듭니다.
import { useState } from 'react';
function InputMirror() {
const [text, setText] = useState(""); // 입력 필드의 텍스트 상태
// 이벤트 핸들러: 입력 필드 값이 변경될 때마다 호출
const handleChange = (e) => { // (1) 이벤트 객체 'e'를 인자로 받습니다.
console.log(e.target.value); // 콘솔에 입력된 값 출력
setText(e.target.value); // (2) e.target.value로 입력 필드의 현재 값에 접근
};
return (
<div style={{ border: '1px solid #c9c9c9', padding: '15px', margin: '20px', borderRadius: '8px' }}>
<h2>입력 필드 미러</h2>
<input
type="text"
value={text}
onChange={handleChange}
placeholder="여기에 입력하세요..."
style={{ padding: '8px', fontSize: '16px', width: '80%', marginBottom: '10px' }}
/>
<p>
입력된 내용: <span style={{ fontWeight: 'bold', color: '#007bff' }}>{text}</span>
</p>
</div>
);
}
export default InputMirror;handleChange는 이벤트 객체를 e라는 이름의 인자로 받습니다. e.target은 이벤트가 시작된 DOM 요소, 여기서는 <input>을 가리킵니다. 비슷한 e.currentTarget은 지금 처리 함수가 붙어 있는 요소입니다. 예를 들어 버튼 안의 span을 클릭하면 target은 span이지만, 버튼에 붙은 처리 함수의 currentTarget은 button입니다.
e.target.value는 그 <input> 요소의 현재 값이며, 이 값을 setText로 text state에 반영합니다. 여기서 <input>의 value 속성을 text state와 연결하는 것이 중요합니다. 그래야 입력 필드의 값이 언제나 state와 같아지고 React가 입력 필드를 제어할 수 있는데, 이런 <input>을 제어 컴포넌트(controlled component)라고 부릅니다.
이제 App.jsx에서 InputMirror를 불러와 씁니다.
import './App.css';
// ... 다른 컴포넌트 임포트 ...
import InputMirror from './components/InputMirror'; // InputMirror 컴포넌트 불러오기
function App() {
return (
<div className="App">
<h1>나의 React 앱</h1>
{/* ... 다른 컴포넌트 ... */}
<InputMirror />
</div>
);
}
export default App;브라우저의 입력 필드에 글자를 입력하면, 아래의 “입력된 내용:” 뒤의 글자도 실시간으로 똑같이 바뀝니다. 이처럼 입력값을 state와 연결하면 사용자의 입력과 화면에 보이는 값이 하나의 데이터 흐름 안에서 함께 움직입니다.
이벤트 핸들러에 인자 전달하기
때로는 이벤트 처리 함수에 추가 인자를 넘겨야 합니다. 여러 아이템 가운데 하나를 클릭했을 때 그 아이템의 ID를 넘기고 싶은 경우가 그렇습니다. 이때는 처리 함수를 화살표 함수로 감싸는 방법을 흔히 씁니다. 아이템 삭제 버튼을 예로 들어 봅니다.
import { useState } from 'react';
function ItemList() {
const [items, setItems] = useState([
{ id: 1, text: '첫 번째 아이템' },
{ id: 2, text: '두 번째 아이템' },
{ id: 3, text: '세 번째 아이템' },
]);
// (1) 삭제할 아이템의 ID를 인자로 받는 함수 정의
const handleDeleteItem = (idToDelete) => {
// filter() 메서드를 사용하여 해당 id를 가진 아이템을 제외하고 새로운 배열 생성
setItems(items.filter(item => item.id !== idToDelete));
};
return (
<div style={{ border: '1px solid #abc', padding: '15px', margin: '20px', borderRadius: '8px' }}>
<h2>아이템 목록</h2>
<ul>
{items.map(item => (
<li key={item.id} style={{ marginBottom: '10px' }}>
{item.text}
{/* (2) 화살표 함수로 감싸서 handleDeleteItem에 item.id를 전달 */}
<button
onClick={() => handleDeleteItem(item.id)}
style={{ marginLeft: '10px', backgroundColor: '#dc3545', color: 'white', border: 'none', padding: '5px 10px', borderRadius: '4px', cursor: 'pointer' }}
>
삭제
</button>
</li>
))}
</ul>
</div>
);
}
export default ItemList;handleDeleteItem은 idToDelete를 인자로 받아, 그 ID를 뺀 새 배열을 setter에 넘깁니다. 원래의 items를 직접 잘라 내는 코드가 아닙니다. 이 예제는 한 번 클릭에 한 번만 삭제하므로 이대로 충분하지만, 같은 이벤트에서 여러 삭제를 이어서 한다면 setItems(prev => prev.filter(...))처럼 큐의 직전 배열을 받아 계산합니다.
button의 onClick에는 () => handleDeleteItem(item.id)라는 화살표 함수를 넘겼습니다. 클릭 이벤트가 일어나면 이 화살표 함수가 실행되고, 그 안에서 handleDeleteItem(item.id)를 호출해 원하는 item.id를 인자로 넘깁니다. 또 리스트를 렌더링할 때는 각 아이템에 key={item.id}처럼 key를 주는 것이 중요합니다. React는 key로 리스트의 각 아이템을 식별하고 효율적으로 갱신합니다. key는 같은 부모의 형제 목록 안에서만 유일하면 되고, 같은 항목에는 바뀌지 않는 값을 써야 합니다. key는 컴포넌트에 props로 전달되지 않으므로, 컴포넌트에서 ID를 읽어야 한다면 key와 별도로 넘깁니다.
이 컴포넌트를 src/components/ItemList.jsx로 저장하고 App에서 불러와 <ItemList />를 보여 주면, 세 아이템과 각각의 삭제 버튼이 나타납니다. ID 2의 삭제 버튼을 누르면 filter가 ID 1과 3만 담은 새 배열을 만들므로, “첫 번째 아이템”과 “세 번째 아이템”만 남습니다.
이벤트 전파(Event Propagation)와 방지
브라우저에서 이벤트는 DOM 트리를 따라 전파됩니다. 예를 들어 버튼을 클릭하면 버튼의 클릭 이벤트뿐 아니라, 그 버튼을 감싼 div와 body 같은 부모 요소에도 클릭 이벤트가 전달되는데, 이를 버블링(bubbling)이라고 하며, 브라우저 이벤트가 거치는 capture 단계와 버블링 단계, 이벤트 위임은 웹 입문 교재의 DOM 조작과 이벤트 처리 기초에서 자세히 다룹니다. 때로는 이런 전파를 막거나, 링크를 클릭했을 때의 페이지 이동이나 폼을 제출했을 때의 새로고침 같은 기본 동작을 막아야 합니다.
e.stopPropagation()은 이벤트가 부모 요소로 전파되는 것을 막습니다. 아래 코드에서 버튼을 클릭하면 “버튼 클릭!” 알림만 뜨고, 부모 div의 알림은 뜨지 않습니다.
<div onClick={() => alert('부모 div 클릭!')}>
<button onClick={(e) => {
e.stopPropagation(); // 버튼 클릭 시 부모 div의 클릭 이벤트는 발생하지 않음
alert('버튼 클릭!');
}}>
버튼
</button>
</div>e.preventDefault()는 이벤트의 기본 동작을 막습니다. <a> 태그의 페이지 이동이나 <form> 태그의 새로고침을 막을 때 씁니다. 아래 링크를 클릭하면 페이지는 이동하지 않고 알림만 뜹니다.
<a href="https://google.com" onClick={(e) => {
e.preventDefault(); // 링크 클릭 시 페이지 이동을 막음
alert('링크 이동이 차단되었습니다!');
}}>
구글로 이동 (차단)
</a>두 메서드는 성능 최적화를 위해 습관처럼 부르는 것이 아니라, 원하는 동작을 먼저 정한 뒤 골라 씁니다. 클릭의 버블링을 멈춰도 링크 이동이라는 기본 동작은 남을 수 있고, 이동만 막아도 부모의 클릭 처리 함수는 실행될 수 있습니다. 이 글은 버블링 단계의 클릭만 다루므로, 별도의 capture 처리 함수나 다른 이벤트에도 똑같이 적용된다고 일반화하지 않습니다(React 문서의 이벤트에 응답하기).
이벤트를 읽을 때는 onClick, onChange 같은 속성의 이름보다 함수가 언제 실행되는지를 먼저 확인하세요. 함수 참조를 넘기는지, 이벤트 객체에서 어떤 값을 읽는지, 필요할 때 전파와 기본 동작을 끊는지까지 따라가 보면 props와 state, 이벤트 처리가 하나의 상호작용 흐름으로 이어집니다.
더 깊이: React가 브라우저 리스너를 실제로 붙이는 곳
JSX에 onClick을 적어도 React가 그 button 요소마다 브라우저의 리스너를 붙이지는 않습니다. React 17부터는 React 트리를 렌더링한 루트 DOM 컨테이너, 이 교재의 예제라면 #root 요소에 리스너를 붙여 두고, 그곳에서 받은 이벤트를 컴포넌트 트리에 맞춰 각 처리 함수에 나눠 줍니다. 이런 방식을 이벤트 위임(event delegation)이라고 합니다. React 16까지는 리스너를 document에 붙였는데, 그러면 한 페이지에 React 트리가 여럿일 때 안쪽 트리에서 전파를 멈춰도 바깥 트리가 이벤트를 받는 문제가 있어 루트 컨테이너로 옮겼습니다(React 17 RC 발표 글).
React 17에서는 이와 함께 바뀐 동작이 몇 가지 더 있습니다. onScroll은 더 이상 버블링되지 않고, onFocus와 onBlur는 내부적으로 브라우저의 focusin과 focusout 이벤트를 쓰며, onClickCapture 같은 capture 처리 함수는 브라우저의 실제 capture 단계 리스너를 씁니다. 또 React 16까지 있던 이벤트 객체의 재사용(event pooling)이 없어졌으므로, 비동기 콜백에서 이벤트의 필드를 읽어도 됩니다. 예전 코드에서 보이는 e.persist()는 지금도 호출할 수 있지만 아무 일도 하지 않습니다.
입력에서 자주 헷갈리는 차이도 하나 있습니다. React의 onChange는 사용자가 값을 바꿀 때마다, 예를 들어 키를 누를 때마다 실행되어 브라우저의 input 이벤트처럼 동작합니다(React 문서의 input). 브라우저의 change 이벤트는 텍스트 입력이라면 보통 값을 바꾼 뒤 포커스를 잃을 때 일어나므로, 순수 자바스크립트 코드를 옮길 때 이벤트 이름만 보고 실행 시점을 짐작하지 않습니다.
정리
- React의 이벤트 속성은
onClick처럼 camelCase로 쓰고, 중괄호 안에 함수를 넘기며, 처리 함수는 합성 이벤트 객체를 받습니다. onClick={handleClick}처럼 함수 자체를 넘겨야 하며,onClick={handleClick()}은 렌더하는 동안 바로 호출됩니다.- 처리 함수에 인자를 넘길 때는
onClick={() => handleDelete(item.id)}처럼 화살표 함수로 감쌉니다. e.target은 이벤트가 시작된 요소,e.currentTarget은 처리 함수가 붙은 요소이며,e.target.value로 입력값을 읽습니다.- 입력의
value를 state와 연결한 제어 컴포넌트에서는 입력과 화면의 값이 하나의 state로 함께 움직입니다. stopPropagation()은 부모로의 전파를,preventDefault()는 기본 동작을 막으며, 둘은 서로를 대신하지 않습니다.
연습 문제
1. 입력을 모두 지우면
InputMirror에 “React”를 입력했다가 모두 지우면, 입력칸과 “입력된 내용:” 뒤의 글자는 어떻게 될까요?
해설 보기
글자를 지울 때마다 onChange가 일어나 setText가 남은 글자로 text를 바꾸므로, 모두 지우면 text는 빈 문자열이 됩니다. 입력칸의 value와 표시 문장이 같은 text를 쓰므로, 입력칸과 “입력된 내용:” 뒤가 함께 비게 됩니다.
2. 차례로 두 번 삭제하기
ItemList에서 ID 2를 삭제한 뒤 ID 1을 삭제하면 어떤 아이템이 남을까요?
해설 보기
첫 삭제 뒤에는 ID 1과 3이 남고, 다음 삭제 뒤에는 ID 3의 “세 번째 아이템”만 남습니다. 두 번째 클릭은 첫 삭제가 반영된 새 items에서 다음 filter를 계산합니다.
3. 전파와 기본 동작 구별하기
부모 div에 클릭 로그를 남기는 처리 함수가 있고, 그 안의 링크에도 클릭 처리 함수가 있습니다. capture 처리 함수는 없으며 링크를 일반적으로 클릭한다고 가정합니다. 링크의 처리 함수가 ① preventDefault()만 호출할 때, ② stopPropagation()만 호출할 때, ③ 둘 다 호출할 때, 부모의 클릭 로그와 링크 이동이 각각 남는지 적어 보세요.
해설 보기
| 경우 | 부모의 클릭 로그 | 링크 이동 |
|---|---|---|
① preventDefault()만 | 남음 | 막힘 |
② stopPropagation()만 | 막힘 | 남음 |
| ③ 둘 다 | 막힘 | 막힘 |
이벤트가 부모로 전달되는 경로와 링크 자체의 기본 동작을 따로 판단하기 때문에 이런 결과가 나옵니다.
4. 괄호를 붙여 넘기면
MessageChanger의 첫 버튼을 onClick={handleClickHello()}로 바꾸면 어떤 일이 생길까요?
해설 보기
handleClickHello()가 렌더하는 동안 바로 호출되어 렌더 도중에 setMessage를 부르게 됩니다. 그러면 React가 다시 렌더링하고, 다시 렌더할 때 또 setMessage가 불리는 일이 되풀이되어, 결국 개발 모드에서 Too many re-renders로 시작하는 오류가 나며 화면이 그려지지 않습니다.
이벤트가 일어날 때 실행하려면 onClick={handleClickHello}처럼 함수 자체를 넘기거나, onClick={() => handleClickHello()}처럼 감싸는 함수를 넘겨야 합니다.
이것으로 2장을 마칩니다. 2장에서는 JSX의 문법과 규칙에서 시작해, 컴포넌트의 개념과 종류, props와 children으로 데이터를 넘기는 방법, state와 useState로 상태를 바꾸는 방법, 그리고 이벤트를 받아 state를 갱신하는 방법까지 React의 기초를 익혔습니다. 다음 장의 조건부 렌더링부터는 조건부 렌더링과 리스트, 폼, 컴포넌트의 생명주기를 다루고, 이를 모아 할 일 목록 앱을 만들어 봅니다.