이벤트 처리 심화
3. 이벤트 처리 심화
React에서 다양한 이벤트를 처리하는 고급 기법들을 배워보겠습니다. 사용자의 입력을 받고, 폼을 처리하며, 복잡한 상호작용을 구현하는 방법을 알아봅시다.
기본 이벤트 처리
클릭 이벤트
import { useState } from 'react';
function ClickExample() {
const [count, setCount] = useState(0);
const [message, setMessage] = useState('버튼을 클릭해보세요');
const handleClick = () => {
setCount(count + 1);
setMessage(`버튼을 ${count + 1}번 클릭했습니다`);
};
const handleReset = () => {
setCount(0);
setMessage('초기화되었습니다');
};
return (
<div>
<h3>클릭 이벤트 예제</h3>
<p>{message}</p>
<p>클릭 횟수: {count}</p>
<button onClick={handleClick}>클릭하기</button>
<button onClick={handleReset}>초기화</button>
</div>
);
}import { useState } from 'react';
function ClickExample() {
const [count, setCount] = useState(0);
const [message, setMessage] = useState('버튼을 클릭해보세요');
const handleClick = () => {
setCount(count + 1);
setMessage(`버튼을 ${count + 1}번 클릭했습니다`);
};
const handleReset = () => {
setCount(0);
setMessage('초기화되었습니다');
};
return (
<div>
<h3>클릭 이벤트 예제</h3>
<p>{message}</p>
<p>클릭 횟수: {count}</p>
<button onClick={handleClick}>클릭하기</button>
<button onClick={handleReset}>초기화</button>
</div>
);
}입력 이벤트
import { useState } from 'react';
function InputExample() {
const [text, setText] = useState('');
const [savedText, setSavedText] = useState('');
const handleInputChange = (event) => {
setText(event.target.value);
};
const handleSave = () => {
setSavedText(text);
setText('');
};
const handleKeyPress = (event) => {
if (event.key === 'Enter') {
handleSave();
}
};
return (
<div>
<h3>입력 이벤트 예제</h3>
<input
type="text"
value={text}
onChange={handleInputChange}
onKeyPress={handleKeyPress}
placeholder="텍스트를 입력하세요"
/>
<button onClick={handleSave}>저장</button>
<p>현재 입력: {text}</p>
<p>저장된 텍스트: {savedText}</p>
</div>
);
}import { useState } from 'react';
function InputExample() {
const [text, setText] = useState('');
const [savedText, setSavedText] = useState('');
const handleInputChange = (event) => {
setText(event.target.value);
};
const handleSave = () => {
setSavedText(text);
setText('');
};
const handleKeyPress = (event) => {
if (event.key === 'Enter') {
handleSave();
}
};
return (
<div>
<h3>입력 이벤트 예제</h3>
<input
type="text"
value={text}
onChange={handleInputChange}
onKeyPress={handleKeyPress}
placeholder="텍스트를 입력하세요"
/>
<button onClick={handleSave}>저장</button>
<p>현재 입력: {text}</p>
<p>저장된 텍스트: {savedText}</p>
</div>
);
}폼 이벤트 처리
간단한 로그인 폼
import { useState } from 'react';
function LoginForm() {
const [formData, setFormData] = useState({
username: '',
password: ''
});
const [isLoggedIn, setIsLoggedIn] = useState(false);
const handleInputChange = (event) => {
const { name, value } = event.target;
setFormData({
...formData,
[name]: value
});
};
const handleSubmit = (event) => {
event.preventDefault(); // 페이지 새로고침 방지
if (formData.username && formData.password) {
setIsLoggedIn(true);
alert('로그인 성공!');
} else {
alert('사용자명과 비밀번호를 모두 입력해주세요');
}
};
const handleLogout = () => {
setIsLoggedIn(false);
setFormData({ username: '', password: '' });
};
if (isLoggedIn) {
return (
<div>
<h3>환영합니다, {formData.username}님!</h3>
<button onClick={handleLogout}>로그아웃</button>
</div>
);
}
return (
<div>
<h3>로그인</h3>
<form onSubmit={handleSubmit}>
<div>
<label>사용자명:</label>
<input
type="text"
name="username"
value={formData.username}
onChange={handleInputChange}
/>
</div>
<div>
<label>비밀번호:</label>
<input
type="password"
name="password"
value={formData.password}
onChange={handleInputChange}
/>
</div>
<button type="submit">로그인</button>
</form>
</div>
);
}import { useState } from 'react';
function LoginForm() {
const [formData, setFormData] = useState({
username: '',
password: ''
});
const [isLoggedIn, setIsLoggedIn] = useState(false);
const handleInputChange = (event) => {
const { name, value } = event.target;
setFormData({
...formData,
[name]: value
});
};
const handleSubmit = (event) => {
event.preventDefault(); // 페이지 새로고침 방지
if (formData.username && formData.password) {
setIsLoggedIn(true);
alert('로그인 성공!');
} else {
alert('사용자명과 비밀번호를 모두 입력해주세요');
}
};
const handleLogout = () => {
setIsLoggedIn(false);
setFormData({ username: '', password: '' });
};
if (isLoggedIn) {
return (
<div>
<h3>환영합니다, {formData.username}님!</h3>
<button onClick={handleLogout}>로그아웃</button>
</div>
);
}
return (
<div>
<h3>로그인</h3>
<form onSubmit={handleSubmit}>
<div>
<label>사용자명:</label>
<input
type="text"
name="username"
value={formData.username}
onChange={handleInputChange}
/>
</div>
<div>
<label>비밀번호:</label>
<input
type="password"
name="password"
value={formData.password}
onChange={handleInputChange}
/>
</div>
<button type="submit">로그인</button>
</form>
</div>
);
}체크박스와 라디오 버튼
import { useState } from 'react';
function SelectionForm() {
const [preferences, setPreferences] = useState({
newsletter: false,
notifications: false,
theme: 'light',
language: 'ko'
});
const handleCheckboxChange = (event) => {
const { name, checked } = event.target;
setPreferences({
...preferences,
[name]: checked
});
};
const handleRadioChange = (event) => {
const { name, value } = event.target;
setPreferences({
...preferences,
[name]: value
});
};
return (
<div>
<h3>설정</h3>
<div>
<h4>알림 설정</h4>
<label>
<input
type="checkbox"
name="newsletter"
checked={preferences.newsletter}
onChange={handleCheckboxChange}
/>
뉴스레터 구독
</label>
<br />
<label>
<input
type="checkbox"
name="notifications"
checked={preferences.notifications}
onChange={handleCheckboxChange}
/>
푸시 알림
</label>
</div>
<div>
<h4>테마 선택</h4>
<label>
<input
type="radio"
name="theme"
value="light"
checked={preferences.theme === 'light'}
onChange={handleRadioChange}
/>
라이트 모드
</label>
<br />
<label>
<input
type="radio"
name="theme"
value="dark"
checked={preferences.theme === 'dark'}
onChange={handleRadioChange}
/>
다크 모드
</label>
</div>
<div>
<h4>언어 선택</h4>
<select
name="language"
value={preferences.language}
onChange={handleRadioChange}
>
<option value="ko">한국어</option>
<option value="en">English</option>
<option value="ja">日本語</option>
</select>
</div>
<div>
<h4>현재 설정</h4>
<p>뉴스레터: {preferences.newsletter ? '구독함' : '구독안함'}</p>
<p>알림: {preferences.notifications ? '켜짐' : '꺼짐'}</p>
<p>테마: {preferences.theme}</p>
<p>언어: {preferences.language}</p>
</div>
</div>
);
}import { useState } from 'react';
function SelectionForm() {
const [preferences, setPreferences] = useState({
newsletter: false,
notifications: false,
theme: 'light',
language: 'ko'
});
const handleCheckboxChange = (event) => {
const { name, checked } = event.target;
setPreferences({
...preferences,
[name]: checked
});
};
const handleRadioChange = (event) => {
const { name, value } = event.target;
setPreferences({
...preferences,
[name]: value
});
};
return (
<div>
<h3>설정</h3>
<div>
<h4>알림 설정</h4>
<label>
<input
type="checkbox"
name="newsletter"
checked={preferences.newsletter}
onChange={handleCheckboxChange}
/>
뉴스레터 구독
</label>
<br />
<label>
<input
type="checkbox"
name="notifications"
checked={preferences.notifications}
onChange={handleCheckboxChange}
/>
푸시 알림
</label>
</div>
<div>
<h4>테마 선택</h4>
<label>
<input
type="radio"
name="theme"
value="light"
checked={preferences.theme === 'light'}
onChange={handleRadioChange}
/>
라이트 모드
</label>
<br />
<label>
<input
type="radio"
name="theme"
value="dark"
checked={preferences.theme === 'dark'}
onChange={handleRadioChange}
/>
다크 모드
</label>
</div>
<div>
<h4>언어 선택</h4>
<select
name="language"
value={preferences.language}
onChange={handleRadioChange}
>
<option value="ko">한국어</option>
<option value="en">English</option>
<option value="ja">日本語</option>
</select>
</div>
<div>
<h4>현재 설정</h4>
<p>뉴스레터: {preferences.newsletter ? '구독함' : '구독안함'}</p>
<p>알림: {preferences.notifications ? '켜짐' : '꺼짐'}</p>
<p>테마: {preferences.theme}</p>
<p>언어: {preferences.language}</p>
</div>
</div>
);
}이벤트 객체 활용
마우스 이벤트 정보
import { useState } from 'react';
function MouseEventExample() {
const [mouseInfo, setMouseInfo] = useState({
x: 0,
y: 0,
button: '',
clickCount: 0
});
const handleMouseMove = (event) => {
setMouseInfo(prev => ({
...prev,
x: event.clientX,
y: event.clientY
}));
};
const handleMouseClick = (event) => {
const buttonNames = ['왼쪽', '휠', '오른쪽'];
setMouseInfo(prev => ({
...prev,
button: buttonNames[event.button] || '알 수 없음',
clickCount: prev.clickCount + 1
}));
};
return (
<div>
<h3>마우스 이벤트 정보</h3>
<div
onMouseMove={handleMouseMove}
onClick={handleMouseClick}
>
마우스를 움직이거나 클릭해보세요
</div>
<p>마우스 위치: ({mouseInfo.x}, {mouseInfo.y})</p>
<p>마지막 클릭 버튼: {mouseInfo.button}</p>
<p>총 클릭 횟수: {mouseInfo.clickCount}</p>
</div>
);
}import { useState } from 'react';
function MouseEventExample() {
const [mouseInfo, setMouseInfo] = useState({
x: 0,
y: 0,
button: '',
clickCount: 0
});
const handleMouseMove = (event) => {
setMouseInfo(prev => ({
...prev,
x: event.clientX,
y: event.clientY
}));
};
const handleMouseClick = (event) => {
const buttonNames = ['왼쪽', '휠', '오른쪽'];
setMouseInfo(prev => ({
...prev,
button: buttonNames[event.button] || '알 수 없음',
clickCount: prev.clickCount + 1
}));
};
return (
<div>
<h3>마우스 이벤트 정보</h3>
<div
onMouseMove={handleMouseMove}
onClick={handleMouseClick}
>
마우스를 움직이거나 클릭해보세요
</div>
<p>마우스 위치: ({mouseInfo.x}, {mouseInfo.y})</p>
<p>마지막 클릭 버튼: {mouseInfo.button}</p>
<p>총 클릭 횟수: {mouseInfo.clickCount}</p>
</div>
);
}키보드 이벤트
import { useState } from 'react';
function KeyboardExample() {
const [keyInfo, setKeyInfo] = useState({
lastKey: '',
keyHistory: [],
specialKeys: []
});
const handleKeyDown = (event) => {
const specialKeys = [];
if (event.ctrlKey) specialKeys.push('Ctrl');
if (event.shiftKey) specialKeys.push('Shift');
if (event.altKey) specialKeys.push('Alt');
setKeyInfo(prev => ({
lastKey: event.key,
keyHistory: [...prev.keyHistory.slice(-9), event.key],
specialKeys: specialKeys
}));
// 특별한 키 조합 처리
if (event.ctrlKey && event.key === 's') {
event.preventDefault();
alert('저장 기능 (Ctrl+S가 눌렸습니다)');
}
};
return (
<div>
<h3>키보드 이벤트</h3>
<input
type="text"
placeholder="키를 눌러보세요"
onKeyDown={handleKeyDown}
/>
<p>마지막 키: {keyInfo.lastKey}</p>
<p>눌린 특수키: {keyInfo.specialKeys.join(', ') || '없음'}</p>
<p>키 히스토리: {keyInfo.keyHistory.join(' → ')}</p>
<p>Ctrl+S를 눌러보세요!</p>
</div>
);
}import { useState } from 'react';
function KeyboardExample() {
const [keyInfo, setKeyInfo] = useState({
lastKey: '',
keyHistory: [],
specialKeys: []
});
const handleKeyDown = (event) => {
const specialKeys = [];
if (event.ctrlKey) specialKeys.push('Ctrl');
if (event.shiftKey) specialKeys.push('Shift');
if (event.altKey) specialKeys.push('Alt');
setKeyInfo(prev => ({
lastKey: event.key,
keyHistory: [...prev.keyHistory.slice(-9), event.key],
specialKeys: specialKeys
}));
// 특별한 키 조합 처리
if (event.ctrlKey && event.key === 's') {
event.preventDefault();
alert('저장 기능 (Ctrl+S가 눌렸습니다)');
}
};
return (
<div>
<h3>키보드 이벤트</h3>
<input
type="text"
placeholder="키를 눌러보세요"
onKeyDown={handleKeyDown}
/>
<p>마지막 키: {keyInfo.lastKey}</p>
<p>눌린 특수키: {keyInfo.specialKeys.join(', ') || '없음'}</p>
<p>키 히스토리: {keyInfo.keyHistory.join(' → ')}</p>
<p>Ctrl+S를 눌러보세요!</p>
</div>
);
}이벤트 전파 제어
이벤트 버블링 예제
import { useState } from 'react';
function BubblingExample() {
const [log, setLog] = useState([]);
const addLog = (message) => {
setLog(prev => [...prev, `${new Date().toLocaleTimeString()}: ${message}`]);
};
const handleParentClick = () => {
addLog('부모 요소 클릭됨');
};
const handleChildClick = () => {
addLog('자식 요소 클릭됨');
};
const handleStopPropagation = (event) => {
event.stopPropagation();
addLog('전파 중단된 자식 클릭됨');
};
const clearLog = () => {
setLog([]);
};
return (
<div>
<h3>이벤트 전파 예제</h3>
<div onClick={handleParentClick}>
부모 요소 (클릭 가능)
<div onClick={handleChildClick}>
일반 자식 요소
</div>
<div onClick={handleStopPropagation}>
전파 중단 자식 요소
</div>
</div>
<button onClick={clearLog}>로그 지우기</button>
<div>
<h4>이벤트 로그:</h4>
<ul>
{log.map((entry, index) => (
<li key={index}>{entry}</li>
))}
</ul>
</div>
</div>
);
}import { useState } from 'react';
function BubblingExample() {
const [log, setLog] = useState([]);
const addLog = (message) => {
setLog(prev => [...prev, `${new Date().toLocaleTimeString()}: ${message}`]);
};
const handleParentClick = () => {
addLog('부모 요소 클릭됨');
};
const handleChildClick = () => {
addLog('자식 요소 클릭됨');
};
const handleStopPropagation = (event) => {
event.stopPropagation();
addLog('전파 중단된 자식 클릭됨');
};
const clearLog = () => {
setLog([]);
};
return (
<div>
<h3>이벤트 전파 예제</h3>
<div onClick={handleParentClick}>
부모 요소 (클릭 가능)
<div onClick={handleChildClick}>
일반 자식 요소
</div>
<div onClick={handleStopPropagation}>
전파 중단 자식 요소
</div>
</div>
<button onClick={clearLog}>로그 지우기</button>
<div>
<h4>이벤트 로그:</h4>
<ul>
{log.map((entry, index) => (
<li key={index}>{entry}</li>
))}
</ul>
</div>
</div>
);
}실습: 간단한 계산기
import { useState } from 'react';
function Calculator() {
const [display, setDisplay] = useState('0');
const [previousValue, setPreviousValue] = useState(null);
const [operation, setOperation] = useState(null);
const [waitingForOperand, setWaitingForOperand] = useState(false);
const inputNumber = (num) => {
if (waitingForOperand) {
setDisplay(String(num));
setWaitingForOperand(false);
} else {
setDisplay(display === '0' ? String(num) : display + num);
}
};
const inputOperation = (nextOperation) => {
const inputValue = parseFloat(display);
if (previousValue === null) {
setPreviousValue(inputValue);
} else if (operation) {
const currentValue = previousValue || 0;
const result = calculate(currentValue, inputValue, operation);
setDisplay(String(result));
setPreviousValue(result);
}
setWaitingForOperand(true);
setOperation(nextOperation);
};
const calculate = (firstValue, secondValue, operation) => {
switch (operation) {
case '+':
return firstValue + secondValue;
case '-':
return firstValue - secondValue;
case '*':
return firstValue * secondValue;
case '/':
return firstValue / secondValue;
case '=':
return secondValue;
default:
return secondValue;
}
};
const performCalculation = () => {
const inputValue = parseFloat(display);
if (previousValue !== null && operation) {
const result = calculate(previousValue, inputValue, operation);
setDisplay(String(result));
setPreviousValue(null);
setOperation(null);
setWaitingForOperand(true);
}
};
const clear = () => {
setDisplay('0');
setPreviousValue(null);
setOperation(null);
setWaitingForOperand(false);
};
return (
<div>
<h3>계산기</h3>
<div>
{display}
</div>
<table>
<tbody>
<tr>
<td><button onClick={clear}>C</button></td>
<td><button onClick={() => inputOperation('/')}>/</button></td>
<td><button onClick={() => inputOperation('*')}>*</button></td>
<td><button onClick={() => inputOperation('-')}>-</button></td>
</tr>
<tr>
<td><button onClick={() => inputNumber(7)}>7</button></td>
<td><button onClick={() => inputNumber(8)}>8</button></td>
<td><button onClick={() => inputNumber(9)}>9</button></td>
<td rowSpan="2"><button onClick={() => inputOperation('+')}>+</button></td>
</tr>
<tr>
<td><button onClick={() => inputNumber(4)}>4</button></td>
<td><button onClick={() => inputNumber(5)}>5</button></td>
<td><button onClick={() => inputNumber(6)}>6</button></td>
</tr>
<tr>
<td><button onClick={() => inputNumber(1)}>1</button></td>
<td><button onClick={() => inputNumber(2)}>2</button></td>
<td><button onClick={() => inputNumber(3)}>3</button></td>
<td rowSpan="2"><button onClick={performCalculation}>=</button></td>
</tr>
<tr>
<td colSpan="2"><button onClick={() => inputNumber(0)}>0</button></td>
<td><button>.</button></td>
</tr>
</tbody>
</table>
</div>
);
}import { useState } from 'react';
function Calculator() {
const [display, setDisplay] = useState('0');
const [previousValue, setPreviousValue] = useState(null);
const [operation, setOperation] = useState(null);
const [waitingForOperand, setWaitingForOperand] = useState(false);
const inputNumber = (num) => {
if (waitingForOperand) {
setDisplay(String(num));
setWaitingForOperand(false);
} else {
setDisplay(display === '0' ? String(num) : display + num);
}
};
const inputOperation = (nextOperation) => {
const inputValue = parseFloat(display);
if (previousValue === null) {
setPreviousValue(inputValue);
} else if (operation) {
const currentValue = previousValue || 0;
const result = calculate(currentValue, inputValue, operation);
setDisplay(String(result));
setPreviousValue(result);
}
setWaitingForOperand(true);
setOperation(nextOperation);
};
const calculate = (firstValue, secondValue, operation) => {
switch (operation) {
case '+':
return firstValue + secondValue;
case '-':
return firstValue - secondValue;
case '*':
return firstValue * secondValue;
case '/':
return firstValue / secondValue;
case '=':
return secondValue;
default:
return secondValue;
}
};
const performCalculation = () => {
const inputValue = parseFloat(display);
if (previousValue !== null && operation) {
const result = calculate(previousValue, inputValue, operation);
setDisplay(String(result));
setPreviousValue(null);
setOperation(null);
setWaitingForOperand(true);
}
};
const clear = () => {
setDisplay('0');
setPreviousValue(null);
setOperation(null);
setWaitingForOperand(false);
};
return (
<div>
<h3>계산기</h3>
<div>
{display}
</div>
<table>
<tbody>
<tr>
<td><button onClick={clear}>C</button></td>
<td><button onClick={() => inputOperation('/')}>/</button></td>
<td><button onClick={() => inputOperation('*')}>*</button></td>
<td><button onClick={() => inputOperation('-')}>-</button></td>
</tr>
<tr>
<td><button onClick={() => inputNumber(7)}>7</button></td>
<td><button onClick={() => inputNumber(8)}>8</button></td>
<td><button onClick={() => inputNumber(9)}>9</button></td>
<td rowSpan="2"><button onClick={() => inputOperation('+')}>+</button></td>
</tr>
<tr>
<td><button onClick={() => inputNumber(4)}>4</button></td>
<td><button onClick={() => inputNumber(5)}>5</button></td>
<td><button onClick={() => inputNumber(6)}>6</button></td>
</tr>
<tr>
<td><button onClick={() => inputNumber(1)}>1</button></td>
<td><button onClick={() => inputNumber(2)}>2</button></td>
<td><button onClick={() => inputNumber(3)}>3</button></td>
<td rowSpan="2"><button onClick={performCalculation}>=</button></td>
</tr>
<tr>
<td colSpan="2"><button onClick={() => inputNumber(0)}>0</button></td>
<td><button>.</button></td>
</tr>
</tbody>
</table>
</div>
);
}정리
이벤트 처리의 심화 내용을 학습했습니다:
- 다양한 이벤트 타입: 클릭, 입력, 키보드, 마우스 이벤트
- 폼 이벤트: 체크박스, 라디오 버튼, 셀렉트 박스 처리
- 이벤트 객체: 이벤트 정보 활용과 특수키 감지
- 이벤트 전파: stopPropagation()을 활용한 버블링 제어
- 실습 예제: 계산기를 통한 복합적인 이벤트 처리
React에서 이벤트를 다룰 때 가장 중요한 것은 상태 업데이트와 이벤트 핸들러를 적절히 연결하는 것입니다. preventDefault()와 stopPropagation() 같은 메서드를 적절히 활용하면 더욱 정교한 사용자 인터랙션을 구현할 수 있습니다.
다음 장에서는 컴포넌트 생명주기와 useEffect에 대해 알아보겠습니다.