간단한 이벤트 처리하기
우리는 지금까지 리액트 컴포넌트를 만들고(App.js
, UserProfile.js
, ToggleSwitch.js
), props
로 데이터를 전달하며(UserProfile.js
), state
를 통해 컴포넌트 내부의 동적인 데이터를 관리(ToggleSwitch.js
)하는 방법을 배웠습니다. 이제 웹 애플리케이션의 핵심인 사용자와의 상호작용을 구현해 볼 차례입니다.
사용자가 버튼을 클릭하거나, 입력 필드에 텍스트를 입력하거나, 마우스를 움직이는 등의 행위를 우리는 이벤트(Event) 라고 부릅니다. 리액트에서는 이러한 이벤트를 어떻게 감지하고 처리하는지 이번 장에서 자세히 알아보겠습니다.
리액트 이벤트 시스템 개요
리액트의 이벤트 시스템은 브라우저의 원시(Native) 이벤트 시스템과 유사하지만, 몇 가지 중요한 차이점이 있습니다.
- JSX 내부에서 이벤트 핸들러 정의: HTML에서
onclick="myFunction()"
과 같이 속성으로 이벤트 핸들러를 정의하는 대신, 리액트 JSX에서는 자바스크립트 함수를 중괄호{}
안에 직접 전달합니다.// HTML <button onclick="alert('클릭!')">클릭</button> // React (JSX) <button onClick={() => alert('클릭!')}>클릭</button>
- CamelCase 속성 이름:
onClick
,onChange
,onMouseOver
등 이벤트 속성 이름은 HTML의 소문자와 달리 CamelCase를 사용합니다. - 합성 이벤트(Synthetic Event): 리액트의 이벤트는 실제 브라우저의 이벤트 객체가 아니라, 리액트가 브라우저의 이벤트를 감싸서 제공하는 합성 이벤트 객체(SyntheticEvent) 입니다. 이 합성 이벤트 객체는 모든 브라우저에서 일관된 방식으로 작동하도록 표준화되어 있어 크로스 브라우징 이슈를 줄여줍니다. 합성 이벤트 객체는
e.stopPropagation()
,e.preventDefault()
와 같은 표준 메서드를 동일하게 제공합니다.
버튼 클릭 이벤트 처리하기
가장 흔하게 사용되는 이벤트 중 하나인 버튼 클릭 이벤트를 처리하는 방법을 살펴보겠습니다.
예제: 메시지 변경 버튼
-
MessageChanger.js
컴포넌트 생성:src/components
폴더 안에MessageChanger.js
파일을 생성합니다.src/components/MessageChanger.js // src/components/MessageChanger.js import React, { 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
두 개의 함수를 정의했습니다. 이 함수들은 버튼이 클릭될 때 호출될 이벤트 핸들러입니다.onClick={handleClickHello}
와 같이button
태그의onClick
속성에 정의된 함수 이름을 전달했습니다. 함수를 호출하는 형태(handleClickHello()
)가 아님에 주의하세요! 함수 호출 형태로 작성하면 컴포넌트가 렌더링될 때마다 함수가 즉시 실행되어 버립니다. 우리는 이벤트가 발생했을 때 함수가 실행되기를 원하므로, 함수 자체를 참조로 전달해야 합니다.
-
App.js
에서MessageChanger
컴포넌트 사용:src/App.js
파일을 열고,MessageChanger
컴포넌트를 불러와 사용합니다.src/App.js // src/App.js import React from 'react'; import './App.css'; // ... 다른 컴포넌트 임포트 ... import MessageChanger from './components/MessageChanger'; // MessageChanger 컴포넌트 불러오기 function App() { return ( <div className="App"> <h1>나의 React 앱</h1> {/* ... 다른 컴포넌트 ... */} <MessageChanger /> </div> ); } export default App;
-
결과 확인: 브라우저를 확인하면 메시지가 표시된 버튼 두 개를 볼 수 있을 것입니다. 각 버튼을 클릭하면 메시지가 동적으로 변경되는 것을 확인해 보세요.
이벤트 객체(e
) 사용하기
이벤트 핸들러 함수는 이벤트가 발생했을 때 해당 이벤트에 대한 정보를 담고 있는 이벤트 객체(SyntheticEvent) 를 인자로 받습니다. 이 객체를 통해 이벤트 발생 요소, 마우스 좌표, 키보드 입력 값 등 다양한 정보에 접근할 수 있습니다.
예제: 입력 필드 값 실시간 반영
-
InputMirror.js
컴포넌트 생성:src/components
폴더 안에InputMirror.js
파일을 생성합니다.src/components/InputMirror.js // src/components/InputMirror.js import React, { 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} // (3) input의 value를 state에 연결 (제어 컴포넌트) onChange={handleChange} // (4) onChange 이벤트 핸들러 등록 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.target.value
는 해당<input>
요소의 현재 값을 의미합니다. 우리는 이 값을setText
함수를 통해text
state
에 업데이트합니다.<input>
태그의value
속성을text
state
와 연결하는 것은 매우 중요합니다. 이를 통해 입력 필드의 값이 항상state
와 동기화되며, 리액트가 입력 필드를 제어할 수 있게 됩니다. 이러한<input>
요소를 제어 컴포넌트(Controlled Component) 라고 부릅니다.
-
App.js
에서InputMirror
컴포넌트 사용:src/App.js // src/App.js import React from 'react'; import './App.css'; // ... 다른 컴포넌트 임포트 ... import InputMirror from './components/InputMirror'; // InputMirror 컴포넌트 불러오기 function App() { return ( <div className="App"> <h1>나의 React 앱</h1> {/* ... 다른 컴포넌트 ... */} <InputMirror /> </div> ); } export default App;
-
결과 확인: 브라우저에서 입력 필드에 텍스트를 입력하면, 아래 텍스트도 실시간으로 똑같이 변경되는 것을 확인할 수 있을 것입니다.
이벤트 핸들러에 인자 전달하기
때로는 이벤트 핸들러 함수에 추가적인 인자를 전달해야 할 때가 있습니다. 예를 들어, 여러 개의 아이템 중 특정 아이템을 클릭했을 때 그 아이템의 ID를 전달하고 싶을 때입니다.
이때는 이벤트 핸들러를 화살표 함수로 감싸서 정의하는 방법이 일반적입니다.
예제: 아이템 삭제 버튼
// src/components/ItemList.js
import React, { 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를 가진 아이템을items
배열에서 제거합니다.onClick={() => handleDeleteItem(item.id)}
:button
의onClick
에는 화살표 함수() => ...
를 사용했습니다. 이 화살표 함수가 클릭 이벤트 발생 시 실행되고, 그 안에서handleDeleteItem(item.id)
를 호출함으로써 원하는item.id
를 인자로 전달할 수 있게 됩니다.key={item.id}
: 리스트를 렌더링할 때 각 아이템에key
props
를 부여하는 것은 매우 중요합니다. 리액트가 리스트의 각 아이템을 효율적으로 식별하고 업데이트하는 데 사용됩니다.key
는 유일해야 하며, 보통 데이터의 고유 ID를 사용합니다.
이벤트 전파(Event Propagation)와 방지
브라우저에서 이벤트는 DOM 트리를 따라 전파됩니다. 예를 들어, 버튼을 클릭하면 버튼에 대한 클릭 이벤트뿐만 아니라, 그 버튼을 감싸고 있는 div
, body
등 부모 요소들에게도 클릭 이벤트가 전달(버블링)됩니다. 때로는 이러한 이벤트 전파를 막거나, 기본 동작(예: 링크 클릭 시 페이지 이동, 폼 제출 시 새로고침)을 방지해야 할 필요가 있습니다.
-
e.stopPropagation()
: 이벤트가 부모 요소로 전파되는 것을 막습니다.<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>
이 두 메서드는 이벤트가 의도치 않은 동작을 유발하거나 성능에 영향을 줄 때 유용하게 사용됩니다.
리액트에서 이벤트를 어떻게 다루는지, onClick
, onChange
와 같은 기본적인 이벤트 핸들러 사용법, 이벤트 객체(e
), 그리고 이벤트 핸들러에 인자를 전달하는 방법까지 상세히 설명했습니다. 마지막으로 stopPropagation
과 preventDefault
를 통해 이벤트 전파 및 기본 동작을 제어하는 방법도 다루었습니다.
이제 여러분은 props
, state
, 그리고 이벤트 처리를 통해 동적인 웹 애플리케이션의 기초를 탄탄히 다지게 되었습니다.