React Hooksでイベントリスナーを使用する際の注意点:間違った動作を防ぐ
React Hooks でイベントリスナーを使用する際の注意点:間違った動作を防ぐ
問題点
この問題は、主に以下の2つの状況で発生します。
- イベントリスナーの登録と解除を適切に行わない
- イベントリスナー内で状態を更新しようとする
イベントリスナーの登録と解除
React Hooks でイベントリスナーを登録する場合、useEffect
フックを使用するのが一般的です。しかし、useEffect
内で登録したイベントリスナーを 解除 しない場合、コンポーネントがアンマウントされた後もイベント処理が実行され続けてしまう可能性があります。
例えば、以下のようなコードは問題があります。
function MyComponent() {
useEffect(() => {
window.addEventListener('click', handleClick);
return () => window.removeEventListener('click', handleClick);
}, []);
return (
<div>Click me</div>
);
}
このコードでは、useEffect
内で window
オブジェクトに click
イベントリスナーを登録しています。しかし、useEffect
の依存関係が空配列なので、このリスナーはコンポーネントがアンマウントされた後も解除されません。
イベントリスナー内で状態を更新
イベントリスナー内で React の状態を更新しようとすると、思わぬ動作が発生する可能性があります。これは、イベントリスナーが非同期に実行されるため、状態更新処理が完了する前にコンポーネントが再レンダリングされてしまう可能性があるからです。
function MyComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
window.addEventListener('click', () => {
setCount(count + 1);
});
}, []);
return (
<div>Count: {count}</div>
);
}
このコードでは、useEffect
内で window
オブジェクトに click
イベントリスナーを登録しています。このリスナーが実行されると、setCount
関数を使用して count
の状態を更新します。しかし、setCount
は非同期に実行されるため、常に正しいカウント値が反映されるとは限りません。
解決策
これらの問題を解決するには、以下の方法が有効です。
イベントリスナーを登録する際には、必ず useEffect
フックの cleanup function を使用して、コンポーネントがアンマウントされた際にリスナーを解除するようにしましょう。
function MyComponent() {
useEffect(() => {
const listener = window.addEventListener('click', handleClick);
return () => window.removeEventListener('click', listener);
}, []);
return (
<div>Click me</div>
);
}
イベントリスナー内で状態を更新する必要がある場合は、useState
フックの代わりに useRef
フックを使用して、イベントリスナー内で参照できる変数を作成しましょう。そして、この変数を使用して状態更新処理を実行します。
function MyComponent() {
const countRef = useRef(0);
useEffect(() => {
const listener = window.addEventListener('click', () => {
countRef.current++;
// ... 状態更新処理
});
return () => window.removeEventListener('click', listener);
}, []);
return (
<div>Count: {countRef.current}</div>
);
}
- イベントリスナーのパフォーマンスに影響を与えないように、イベントリスナーの使用を最小限に抑えるようにしましょう。
- React SyntheticEvent を使用する場合は、イベントリスナー内で直接状態を更新しても問題ありません。
- カスタムフックを使用してイベントリスナーを管理するのも有効な方法です。
function MyComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
const handleClick = () => {
setCount(count + 1);
};
const listener = window.addEventListener('click', handleClick);
return () => window.removeEventListener('click', listener);
}, []);
return (
<div>
<button onClick={handleClick}>Click me</button>
<p>Count: {count}</p>
</div>
);
}
コンポーネントがアンマウントされるときに、useEffect
フックの cleanup function が実行され、イベントリスナーが解除されます。
function MyComponent() {
const countRef = useRef(0);
useEffect(() => {
const handleClick = () => {
countRef.current++;
};
const listener = window.addEventListener('click', handleClick);
return () => window.removeEventListener('click', listener);
}, []);
return (
<div>
<button onClick={handleClick}>Click me</button>
<p>Count: {countRef.current}</p>
</div>
);
}
イベントリスナーの登録・解除や状態更新処理をカプセル化するために、カスタムフックを作成することができます。これにより、コードをより整理し、再利用しやすくなります。
例:
function useWindowEvent(eventName, handler) {
const savedHandler = useRef(handler);
useEffect(() => {
const listener = (event) => savedHandler.current(event);
window.addEventListener(eventName, listener);
return () => window.removeEventListener(eventName, listener);
}, []);
return {};
}
function MyComponent() {
const handleClick = (event) => {
console.log(`Clicked! ${event.target.textContent}`);
};
useWindowEvent('click', handleClick);
return (
<div>
<button>Click me</button>
</div>
);
}
この例では、useWindowEvent
というカスタムフックを作成し、イベント名とイベントハンドラーを引数として受け取ります。フック内部では、useEffect
フックを使用してイベントリスナーを登録し、コンポーネントがアンマウントされるときに解除します。
React Context を使用する
イベントリスナーを複数のコンポーネント間で共有したい場合は、React Context を使用することができます。Context にイベントリスナー登録・解除のための関数や、状態更新のための関数などを提供することで、コンポーネント間で簡単に共有することができます。
const EventContext = React.createContext({
addEventListener: () => {},
removeEventListener: () => {},
updateState: () => {},
});
function EventProvider({ children }) {
const [count, setCount] = useState(0);
const addEventListener = (eventName, handler) => {
// ... イベントリスナー登録処理
};
const removeEventListener = (eventName, handler) => {
// ... イベントリスナー解除処理
};
const updateState = (newState) => {
setCount(newState);
};
const value = {
addEventListener,
removeEventListener,
updateState,
count,
};
return (
<EventContext.Provider value={value}>
{children}
</EventContext.Provider>
);
}
function MyComponent() {
const { addEventListener, updateState } = useContext(EventContext);
useEffect(() => {
addEventListener('click', (event) => {
updateState(event.target.textContent);
});
}, [addEventListener, updateState]);
return (
<div>
<button>Click me</button>
<p>Count: {count}</p>
</div>
);
}
この例では、EventContext
という Context を作成し、イベントリスナー登録・解除のための関数、状態更新のための関数、および count
の状態を提供しています。EventProvider
コンポーネントは、これらの値を Context に提供します。そして、MyComponent
コンポーネントは Context を使ってこれらの値にアクセスし、イベントリスナーを登録したり、状態を更新したりすることができます。
サードパーティライブラリを使用する
React Hooks以外にも、イベントリスナーを扱うためのサードパーティライブラリがいくつか存在します。これらのライブラリは、より高度な機能や、特定のユースケースに特化した機能を提供することがあります。
これらの方法は、それぞれ異なる利点と欠点があります。状況に応じて適切な方法を選択することが重要です。
- コードが読みやすく、理解しやすいように、適切なコメントを記述しましょう。
- イベントリスナー内で副作用が発生する場合は、
useEffect
フックの第二引数に依存関係を指定して、適切なタイミングで実行されるようにしましょう。
javascript reactjs react-hooks