【React】ブラウザのウィンドウサイズを自動検知する
はじめに
以降にカスタムフックのコード例を記載しますが、全て以下のcreate-react-app
で作成された初期のApp.tsx
ベースのコードで動きます。
どのカスタムフックのパターンでも呼び出し側のコードは同じで、同じ動作となります。
import "./App.css";
import { useWindowSize } from "./hooks/useWindowSize";
function App() {
const { windowWidth, windowHeight } = useWindowSize();
// const [windowWidth, windowHeight] = useWindowSize(); // 配列で返すパターンの場合
return (
<div className="App">
<header className="App-header">
<p>windowWidth: {windowWidth}</p>
<p>windowHeight: {windowHeight}</p>
</header>
</div>
);
}
export default App;
React18 以前
実装するにはuseState
と、useEffect
(useLayoutEffect
)を組み合わせて実装する方法がメジャーかなと思います。
以下にサンプルで 3 つのパターンを記載します。
やってることはほぼ一緒なので、好みやプロジェクトの方針に沿ったものを使用してやってください。
type WindowSize = {
windowWidth: number;
windowHeight: number;
};
export const useWindowSize = (): WindowSize => {
const [windowSize, setWindowSize] = useState({
windowWidth: window.innerWidth,
windowHeight: window.innerHeight,
});
useEffect(() => {
const resize = () => {
setWindowSize({
windowWidth: window.innerWidth,
windowHeight: window.innerHeight,
});
};
window.addEventListener("resize", resize);
return () => window.removeEventListener("resize", resize);
}, []);
return windowSize;
};
export const useWindowSize = (): number[] => {
const [windowSize, setWindowSize] = useState([0, 0]);
useEffect(() => {
const resize = (): void => {
setWindowSize([window.innerWidth, window.innerHeight]);
};
window.addEventListener("resize", resize);
return () => window.removeEventListener("resize", resize);
}, []);
return windowSize;
};
type WindowSize = {
windowWidth: number;
windowHeight: number;
};
export const useWindowSize = () => {
const getWindowSize = () => {
const { innerWidth: windowWidth, innerHeight: windowHeight } = window;
return {
windowWidth,
windowHeight,
};
};
const [windowSize, setWindowSize] = useState(getWindowSize());
useEffect(() => {
const resize = () => {
setWindowSize(getWindowSize());
};
window.addEventListener("resize", resize);
return () => window.removeEventListener("resize", resize);
}, []);
return windowSize;
};
useEffect
と useLayoutEffect
の違いの詳細はここでは割愛しますので、以下の記事などを参照ください。
(とりあえずuseEffect
で実装しておいて、レンダリング時のちらつきやパフォーマンスに問題がある場合はuseLayoutEffect
を検討するぐらいでいいのでは、と個人的には思ってます。)
React18 以降
React18 から新たに追加されたuseSyncExternalStore
を使うことで、より簡単にウィンドウサイズを検知できるようになりました。
今まで useState
とuseEffect
などを組みわせて実装していたものを、useSyncExternalStore
が内部的によしなにやってくれるようになります。
useSyncExternalStore
のことを簡単に説明すると、React の状態管理の管轄外(外部)にある状態を React の状態と同期させるためのフックです。
例えば、ウィンドウサイズ、ネットワーク状態、FireStore との同期、などなど。
詳細は公式ドキュメントを参照してください。
import { useSyncExternalStore } from "react";
type WindowSize = {
windowWidth: number;
windowHeight: number;
};
const getWindowWidth = () => {
return window.innerWidth;
};
const getWindowHeight = () => {
return window.innerHeight;
};
const subscribeWindowSizeChange = (callback: () => void) => {
window.addEventListener("resize", callback);
return () => window.removeEventListener("resize", callback);
};
export const useWindowSize = (): WindowSize => {
const width = useSyncExternalStore(subscribeWindowSizeChange, getWindowWidth);
const height = useSyncExternalStore(
subscribeWindowSizeChange,
getWindowHeight
);
return { windowWidth: width, windowHeight: height };
};
まとめ
- React18 採用しているプロジェクトなら、
useSyncExternalStore
を使うのが簡単で良さそう。 - React18 未満のプロジェクトなら、
useState
とuseEffect
の組み合わせて実装すれば対応可能。
useEffect
かuseLayoutEffect
かは、実装方針や方針に合わせて選択してください。
NCDC株式会社( ncdc.co.jp/ )のエンジニアチームです。 募集中のエンジニアのポジションや、採用している技術スタックの紹介などはこちら( github.com/ncdcdev/recruitment )をご覧ください! ※エンジニア以外も記事を投稿することがあります
Discussion