React vs Vanilla JavaScript イベント制御比較表
Reactのイベント管理は2層構造になっています:
// JSX内のイベント → Reactが自動でライフサイクル管理
<button onClick={handleClick}>クリック</button>
<input onChange={handleChange} />
-
自動登録・削除: コンポーネントのマウント/アンマウントで自動処理
-
SyntheticEvent: ブラウザ間の差異を吸収
-
最適化: イベント委譲で効率的
// React外の領域 → useEffectで手動クリーンアップ
useEffect(() => {
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize); // 手動削除
}, []);
-
グローバルイベント: window, document, body等
-
サードパーティライブラリ: React外のDOM操作
-
特殊なDOM API: Intersection Observer, Mutation Observer等
-
柔軟性: 完全抽象化せず、低レベルAPIも使える
-
責任分離: React管理範囲を明確化
-
互換性: 既存のJavaScriptライブラリとの連携
// 方法1: keyupイベント
const textbox = document.getElementById('myInput');
textbox.addEventListener('keyup', (event) => {
const value = event.target.value;
console.log('入力値:', value);
});
// 方法2: inputイベント(推奨)
textbox.addEventListener('input', (event) => {
const value = event.target.value;
console.log('入力値:', value);
});
// 方法3: MutationObserver
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'attributes' && mutation.attributeName === 'value') {
console.log('値変更:', mutation.target.value);
}
});
});
observer.observe(textbox, { attributes: true });
// 方法1: onChange(推奨・標準)
function MyComponent() {
const [value, setValue] = useState('');
const handleChange = (event) => {
setValue(event.target.value);
console.log('入力値:', event.target.value);
};
return <input value={value} onChange={handleChange} />;
}
// 方法2: onInput(あまり使わない)
function MyComponent() {
const [value, setValue] = useState('');
return (
<input
value={value}
onInput={(e) => setValue(e.target.value)}
/>
);
}
// useRef + イベントリスナー(Reactらしくない)
function MyComponent() {
const inputRef = useRef(null);
useEffect(() => {
const input = inputRef.current;
const handleInput = (e) => console.log(e.target.value);
input.addEventListener('input', handleInput);
return () => input.removeEventListener('input', handleInput);
}, []);
return <input ref={inputRef} />;
}
// 方法1: addEventListener
const button = document.getElementById('myButton');
button.addEventListener('click', (event) => {
console.log('クリックされました');
event.preventDefault(); // デフォルト動作を防ぐ
});
// 方法2: onclickプロパティ
button.onclick = function(event) {
console.log('クリックされました');
};
// 方法3: HTML属性(非推奨)
// <button onclick="handleClick()">クリック</button>
// 方法1: onClick(標準)
function MyComponent() {
const handleClick = (event) => {
console.log('クリックされました');
event.preventDefault(); // SyntheticEvent
};
return <button onClick={handleClick}>クリック</button>;
}
// 方法2: インライン関数
function MyComponent() {
return (
<button onClick={() => console.log('クリック')}>
クリック
</button>
);
}
// 方法3: useCallback(パフォーマンス最適化)
function MyComponent() {
const handleClick = useCallback(() => {
console.log('クリックされました');
}, []);
return <button onClick={handleClick}>クリック</button>;
}
// useRef + addEventListener(Reactらしくない)
function MyComponent() {
const buttonRef = useRef(null);
useEffect(() => {
const button = buttonRef.current;
const handleClick = () => console.log('クリック');
button.addEventListener('click', handleClick);
return () => button.removeEventListener('click', handleClick);
}, []);
return <button ref={buttonRef}>クリック</button>;
}
// 方法1: submit イベント
const form = document.getElementById('myForm');
form.addEventListener('submit', (event) => {
event.preventDefault();
const formData = new FormData(form);
console.log('送信データ:', Object.fromEntries(formData));
});
// 方法2: ボタンクリックで手動送信
const submitButton = document.getElementById('submitBtn');
submitButton.addEventListener('click', (event) => {
event.preventDefault();
// 手動でフォームデータを収集
const name = document.getElementById('name').value;
const email = document.getElementById('email').value;
console.log({ name, email });
});
// 方法1: onSubmit(推奨)
function MyForm() {
const [formData, setFormData] = useState({ name: '', email: '' });
const handleSubmit = (event) => {
event.preventDefault();
console.log('送信データ:', formData);
};
const handleChange = (event) => {
const { name, value } = event.target;
setFormData(prev => ({ ...prev, [name]: value }));
};
return (
<form onSubmit={handleSubmit}>
<input name="name" value={formData.name} onChange={handleChange} />
<input name="email" value={formData.email} onChange={handleChange} />
<button type="submit">送信</button>
</form>
);
}
🟡 React管理 + 手動管理 - 場合により有効
// useRef + FormData(非制御コンポーネント)
function MyForm() {
const formRef = useRef(null);
const handleSubmit = (event) => {
event.preventDefault();
const formData = new FormData(formRef.current);
console.log('送信データ:', Object.fromEntries(formData));
};
return (
<form ref={formRef} onSubmit={handleSubmit}>
<input name="name" />
<input name="email" />
<button type="submit">送信</button>
</form>
);
}
// ホバー
const element = document.getElementById('myElement');
element.addEventListener('mouseenter', () => {
console.log('マウス進入');
});
element.addEventListener('mouseleave', () => {
console.log('マウス退出');
});
// ドラッグ
let isDragging = false;
element.addEventListener('mousedown', (e) => {
isDragging = true;
console.log('ドラッグ開始');
});
document.addEventListener('mousemove', (e) => {
if (isDragging) {
console.log('ドラッグ中:', e.clientX, e.clientY);
}
});
document.addEventListener('mouseup', () => {
isDragging = false;
console.log('ドラッグ終了');
});
// ホバー
function MyComponent() {
const [isHovered, setIsHovered] = useState(false);
return (
<div
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
style={{ backgroundColor: isHovered ? 'lightblue' : 'white' }}
>
ホバーしてください
</div>
);
}
🟡 React管理 + 手動管理 - 場合により有効
// ドラッグ(グローバルイベントが必要)
function DraggableComponent() {
const [isDragging, setIsDragging] = useState(false);
const [position, setPosition] = useState({ x: 0, y: 0 });
const handleMouseDown = () => setIsDragging(true); // React管理
const handleMouseMove = (e) => {
if (isDragging) {
setPosition({ x: e.clientX, y: e.clientY });
}
};
const handleMouseUp = () => setIsDragging(false);
useEffect(() => {
if (isDragging) {
// グローバルイベント = 手動管理
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
return () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
}
}, [isDragging]);
return (
<div
onMouseDown={handleMouseDown} // React管理
style={{ position: 'absolute', left: position.x, top: position.y }}
>
ドラッグできます
</div>
);
}
// グローバルキーイベント
document.addEventListener('keydown', (event) => {
if (event.key === 'Enter') {
console.log('Enterキー押下');
}
if (event.ctrlKey && event.key === 's') {
event.preventDefault();
console.log('Ctrl+S 保存ショートカット');
}
});
// 特定要素のキーイベント
const input = document.getElementById('myInput');
input.addEventListener('keydown', (event) => {
if (event.key === 'Escape') {
input.blur(); // フォーカス外す
}
});
// 要素固有のキーイベント
function MyInput() {
const inputRef = useRef(null);
const handleKeyDown = (event) => {
if (event.key === 'Escape') {
inputRef.current.blur();
}
if (event.key === 'Enter') {
console.log('入力確定:', event.target.value);
}
};
return <input ref={inputRef} onKeyDown={handleKeyDown} />;
}
// グローバルキーイベント
function MyComponent() {
useEffect(() => {
const handleKeyDown = (event) => {
if (event.key === 'Enter') {
console.log('Enterキー押下');
}
if (event.ctrlKey && event.key === 's') {
event.preventDefault();
console.log('Ctrl+S 保存ショートカット');
}
};
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}, []);
return <div>キーイベント監視中</div>;
}
// ページロード
window.addEventListener('load', () => {
console.log('ページ完全読み込み完了');
});
document.addEventListener('DOMContentLoaded', () => {
console.log('DOM構築完了');
});
// ページアンロード
window.addEventListener('beforeunload', (event) => {
event.preventDefault();
event.returnValue = ''; // 確認ダイアログ表示
});
window.addEventListener('unload', () => {
console.log('ページ離脱');
});
// コンポーネントマウント・アンマウント
function MyComponent() {
useEffect(() => {
// マウント時(ページロード相当)
console.log('コンポーネントマウント');
// アンマウント時(ページアンロード相当)
return () => {
console.log('コンポーネントアンマウント');
};
}, []);
return <div>コンポーネント</div>;
}
// ページ離脱防止
function MyComponent() {
useEffect(() => {
const handleBeforeUnload = (event) => {
event.preventDefault();
event.returnValue = '';
};
window.addEventListener('beforeunload', handleBeforeUnload);
return () => window.removeEventListener('beforeunload', handleBeforeUnload);
}, []);
return <div>コンポーネント</div>;
}
項目 |
Vanilla JavaScript |
React |
イベント登録 |
addEventListener |
JSX属性(onClick , onChange 等) |
イベント削除 |
removeEventListener |
useEffect のクリーンアップ |
状態管理 |
手動でDOM更新 |
useState で自動再レンダリング |
パフォーマンス |
手動最適化が必要 |
Virtual DOM で自動最適化 |
クリーンアップ |
手動で管理 |
useEffect で自動管理 |
デバッグ |
ブラウザDevTools |
React DevTools + ブラウザ |
-
宣言的: 「どう見えるか」を記述
-
自動管理: イベントリスナーの登録・削除
-
統一性: 全てのイベントがSyntheticEvent
-
最適化: 不要な更新を自動で防ぐ
-
直接制御: より細かい制御が可能
-
軽量: フレームワークの overhead なし
-
柔軟性: 任意のDOM操作が可能
React イベントハンドラー一覧(onXXXX)
イベント |
説明 |
主要ブラウザイベント |
使用例 |
手動管理 |
onClick |
クリック |
click |
<button onClick={handleClick}> |
非推奨 |
onDoubleClick |
ダブルクリック |
dblclick |
<div onDoubleClick={handleDouble}> |
非推奨 |
onMouseDown |
マウスボタン押下 |
mousedown |
<div onMouseDown={handleMouseDown}> |
非推奨 |
onMouseUp |
マウスボタン離す |
mouseup |
<div onMouseUp={handleMouseUp}> |
非推奨 |
onMouseEnter |
マウス進入 |
mouseenter |
<div onMouseEnter={handleEnter}> |
非推奨 |
onMouseLeave |
マウス退出 |
mouseleave |
<div onMouseLeave={handleLeave}> |
非推奨 |
onMouseOver |
マウスオーバー(バブリング有) |
mouseover |
<div onMouseOver={handleOver}> |
非推奨 |
onMouseOut |
マウスアウト(バブリング有) |
mouseout |
<div onMouseOut={handleOut}> |
非推奨 |
onMouseMove |
マウス移動 |
mousemove |
<div onMouseMove={handleMove}> |
+React管理で有効 |
onContextMenu |
右クリックメニュー |
contextmenu |
<div onContextMenu={handleContext}> |
非推奨 |
イベント |
説明 |
主要ブラウザイベント |
使用例 |
手動管理 |
onKeyDown |
キー押下 |
keydown |
<input onKeyDown={handleKeyDown}> |
グローバルで有効 |
onKeyUp |
キー離す |
keyup |
<input onKeyUp={handleKeyUp}> |
グローバルで有効 |
onKeyPress |
キー押下(非推奨) |
keypress |
<input onKeyPress={handleKeyPress}> |
非推奨 |
イベント |
説明 |
主要ブラウザイベント |
使用例 |
手動管理 |
onChange |
値変更 |
change, input |
<input onChange={handleChange}> |
非推奨 |
onInput |
入力イベント |
input |
<input onInput={handleInput}> |
非推奨 |
onSubmit |
フォーム送信 |
submit |
<form onSubmit={handleSubmit}> |
非推奨 |
onReset |
フォームリセット |
reset |
<form onReset={handleReset}> |
非推奨 |
onFocus |
フォーカス取得 |
focus |
<input onFocus={handleFocus}> |
非推奨 |
onBlur |
フォーカス失う |
blur |
<input onBlur={handleBlur}> |
非推奨 |
onSelect |
テキスト選択 |
select |
<input onSelect={handleSelect}> |
非推奨 |
イベント |
説明 |
主要ブラウザイベント |
使用例 |
手動管理 |
onDrag |
ドラッグ中 |
drag |
<div onDrag={handleDrag}> |
+React管理で有効 |
onDragStart |
ドラッグ開始 |
dragstart |
<div onDragStart={handleDragStart}> |
非推奨 |
onDragEnd |
ドラッグ終了 |
dragend |
<div onDragEnd={handleDragEnd}> |
非推奨 |
onDragEnter |
ドラッグ要素が進入 |
dragenter |
<div onDragEnter={handleDragEnter}> |
非推奨 |
onDragLeave |
ドラッグ要素が退出 |
dragleave |
<div onDragLeave={handleDragLeave}> |
非推奨 |
onDragOver |
ドラッグ要素が上を通過 |
dragover |
<div onDragOver={handleDragOver}> |
非推奨 |
onDrop |
ドロップ |
drop |
<div onDrop={handleDrop}> |
非推奨 |
イベント |
説明 |
主要ブラウザイベント |
使用例 |
手動管理 |
onCopy |
コピー |
copy |
<div onCopy={handleCopy}> |
グローバルで有効 |
onCut |
カット |
cut |
<div onCut={handleCut}> |
グローバルで有効 |
onPaste |
ペースト |
paste |
<input onPaste={handlePaste}> |
グローバルで有効 |
イベント |
説明 |
主要ブラウザイベント |
使用例 |
手動管理 |
onTouchStart |
タッチ開始 |
touchstart |
<div onTouchStart={handleTouchStart}> |
非推奨 |
onTouchMove |
タッチ移動 |
touchmove |
<div onTouchMove={handleTouchMove}> |
+React管理で有効 |
onTouchEnd |
タッチ終了 |
touchend |
<div onTouchEnd={handleTouchEnd}> |
非推奨 |
onTouchCancel |
タッチキャンセル |
touchcancel |
<div onTouchCancel={handleTouchCancel}> |
非推奨 |
イベント |
説明 |
主要ブラウザイベント |
使用例 |
手動管理 |
onWheel |
マウスホイール |
wheel |
<div onWheel={handleWheel}> |
非推奨 |
onScroll |
スクロール |
scroll |
<div onScroll={handleScroll}> |
グローバルで有効 |
イベント |
説明 |
主要ブラウザイベント |
使用例 |
手動管理 |
onPlay |
再生開始 |
play |
<video onPlay={handlePlay}> |
非推奨 |
onPause |
一時停止 |
pause |
<video onPause={handlePause}> |
非推奨 |
onEnded |
再生終了 |
ended |
<video onEnded={handleEnded}> |
非推奨 |
onLoadStart |
読み込み開始 |
loadstart |
<video onLoadStart={handleLoadStart}> |
非推奨 |
onLoad |
読み込み完了 |
load |
<img onLoad={handleLoad}> |
非推奨 |
onError |
エラー発生 |
error |
<img onError={handleError}> |
非推奨 |
イベント |
説明 |
主要ブラウザイベント |
使用例 |
手動管理 |
onAnimationStart |
アニメーション開始 |
animationstart |
<div onAnimationStart={handleAnimStart}> |
非推奨 |
onAnimationEnd |
アニメーション終了 |
animationend |
<div onAnimationEnd={handleAnimEnd}> |
非推奨 |
onAnimationIteration |
アニメーション反復 |
animationiteration |
<div onAnimationIteration={handleAnimIter}> |
非推奨 |
onTransitionEnd |
トランジション終了 |
transitionend |
<div onTransitionEnd={handleTransEnd}> |
非推奨 |
イベント |
説明 |
主要ブラウザイベント |
使用例 |
手動管理 |
onResize |
リサイズ |
resize |
<div onResize={handleResize}> |
グローバルで有効 |
onToggle |
details要素の開閉 |
toggle |
<details onToggle={handleToggle}> |
非推奨 |
-
非推奨: React管理を使うべき
-
+React管理で有効: グローバルイベントと組み合わせる場合に有効
-
グローバルで有効: window/documentレベルのイベント監視で有効
// 複数イベントの組み合わせ例
function InteractiveComponent() {
const [state, setState] = useState({
isHovered: false,
isDragging: false,
value: ''
});
return (
<div
onMouseEnter={() => setState(s => ({...s, isHovered: true}))}
onMouseLeave={() => setState(s => ({...s, isHovered: false}))}
onDragStart={() => setState(s => ({...s, isDragging: true}))}
onDragEnd={() => setState(s => ({...s, isDragging: false}))}
>
<input
value={state.value}
onChange={(e) => setState(s => ({...s, value: e.target.value}))}
onFocus={() => console.log('フォーカス')}
onBlur={() => console.log('ブラー')}
onKeyDown={(e) => {
if (e.key === 'Enter') {
console.log('エンター押下');
}
}}
/>
</div>
);
}