Table of contents
Resize 이벤트
인터넷에 검색했을 때 화면 사이즈가 변경되는 걸 감지하는 방법은 resize
이벤트를 감지하여 콜백 함수를 실행하게 하는 것이다.
브라우저 창이 최소 1픽셀씩 변경될 때마다 매번 콜백 함수가 실행되므로 비효율적이며 웹 페이지의 성능에 악영향을 줄 수 있다.
창 크기가 변경되었을 때 변경된 엘리먼트의 크기를 구하려면 getBoundingClientRect
메소드를 실행해야 하는데 호출하게 되면 브라우저는 모든 엘리먼트의 스타일과 레이아웃을 재계산하는 Reflow가 발생한다.
useEffect(() => {
useEffect(() => {
const onResize = function (event: UIEvent) {
console.log("Resizing...");
};
window.addEventListener("resize", onResize);
return () => {
window.removeEventListener("resize", onResize);
};
}, []);
}, []);
Resize Observer
Resize Observer
로 특정 엘리먼트가 변화할 때 함수를 호출하도록 할 수 있다.
브라우저 창이 변경하면 document.body
엘리먼트가 변화하는 걸 이용하여 옵저버로 body 엘리먼트를 관찰하게 한다.
각 엔트리마다 contentRect로 엘리먼트의 레이아웃 정보를 가져올 수 있다.
React 프로젝트에서는 useEffect에 옵저버를 선언 후 엘리먼트를 관찰하고 관찰을 해제하는 함수를 반환하도록 한다.
const onResize = useCallback(() => {
console.log("Resizing...");
}, []);
useEffect(() => {
const observer = new ResizeObserver((entries) => {
entries.forEach((entry) => {
if (entry.target.isSameNode(document.body)) {
console.log(entry.contentRect);
onResize();
}
});
});
observer.observe(document.body);
return () => {
observer.disconnect();
};
}, [onResize]);
useMediaQuery
만약 특정 사이즈 이하일 때만 컴포넌트를 렌더링하거나 특정 함수를 실행시키고 싶으면 Window 객체의 matchMedia
메소드를 적용할 수 있다.
메소드가 반환하는 객체에 이벤트 리스너를 설정해서 창 크기가 변할 때마다 인자로 주어진 조건에 해당하는지 알 수 있다.
React 프론트엔드에서는 window 객체의 상태와 동기화하기 위해 useSyncExternalStore
를 사용할 수 있다.
- SSR 환경에서는 서버에서도 렌더링이 발생하기 때문에
getServerSnapshot
인자도 전달해야 한다.
// ./src/hooks/useMediaQuery.ts
import { useCallback, useSyncExternalStore } from "react";
export interface UseMediaQueryProps {
query?: string;
}
const getServerSnapshot = () => null;
const useMediaQuery = (props: UseMediaQueryProps) => {
const { query } = props;
const onSubscribe = useCallback(
(onStoreChange: () => unknown) => {
if (!query) {
return () => {};
}
const matchMedia = window.matchMedia(query);
matchMedia.addEventListener("change", onStoreChange);
return () => {
matchMedia.removeEventListener("change", onStoreChange);
};
},
[query]
);
const onSnapshot = useCallback(() => {
if (!query) {
return false;
}
return window.matchMedia(query).matches;
}, [query]);
return useSyncExternalStore(onSubscribe, onSnapshot, getServerSnapshot);
};
export default useMediaQuery;
// ./src/Example.tsx
export default function Example() {
const isMobileSize = useMediaQuery({
query: "(max-width: 480px)",
});
const isTabletSize = useMediaQuery({
query: "(min-width: 480px) and (max-width: 720px)",
});
if (isMobileSize) {
return <ExampleMobile />;
}
if (isTabletSize) {
return <ExampleTablet />;
}
return <Example />;
}