在 Next.js 中创建自定义 React hook 来获取窗口尺寸
经典实现
SSR地狱
解决方案
在使用 React App 的前端时,您可能需要在某个时候访问窗口的尺寸。
经典实现
为了保持代码 DRY 原则,一个普遍的良好做法是将此操作外部化到自定义的 React hook 中。
例如:
// useWindowDimension.js
const [width, setWidth] = useState(window.innerWidth);
const [height, setHeight] = useState(window.innerHeight);
const updateDimensions = () => {
setWidth(window.innerWidth);
setHeight(window.innerHeight);
}
useEffect(() => {
window.addEventListener("resize", updateDimensions);
return () => window.removeEventListener("resize", updateDimensions);
}, []);
return { width, height};
虽然在使用 React(如 create-react-app)构建的传统客户端应用程序中一切运行良好,但 Gatsby 或 Next.js 中出现了问题。
SSR地狱
Next 和 Gatsby 的主要问题是它们在 FE 和 BE 上都运行代码……window
显然没有定义 where 在哪里。
那么,我听到有人问,该如何解决这个问题呢?
好吧,您可以写类似这样的内容,在继续之前检查窗口是否已定义。
// useWindowDimension.js
import { useState, useEffect } from 'react';
export default function useWindowDimensions() {
const hasWindow = typeof window !== 'undefined';
function getWindowDimensions() {
const width = hasWindow ? window.innerWidth : null;
const height = hasWindow ? window.innerHeight : null;
return {
width,
height,
};
}
const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions());
useEffect(() => {
if (hasWindow) {
function handleResize() {
setWindowDimensions(getWindowDimensions());
}
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}
}, [hasWindow]);
return windowDimensions;
}
请注意,在撰写本文时,这是Stackoverflow上关于 Next.js 实现的投票最高的答案。
但是,尝试此代码会在 Next.js 中触发警告:
那么,为什么这个代码有缺陷,我们如何才能使它万无一失?
解决方案
直到读了Josh 的《W Comeau》之后,我才意识到问题所在。上面的实现实际上是通过检查窗口对象是否定义来绕过 Rehydration 过程的
! 更好的实现应该是确保组件已经挂载(并使用useEffect
钩子)。
最终的自定义钩子看起来像这样,每个人都很高兴!
/**
* // useWindowDimension.ts
* * This hook returns the viewport/window height and width
*/
import { useEffect, useState } from 'react';
type WindowDimentions = {
width: number | undefined;
height: number | undefined;
};
const useWindowDimensions = (): WindowDimentions => {
const [windowDimensions, setWindowDimensions] = useState<WindowDimentions>({
width: undefined,
height: undefined,
});
useEffect(() => {
function handleResize(): void {
setWindowDimensions({
width: window.innerWidth,
height: window.innerHeight,
});
}
handleResize();
window.addEventListener('resize', handleResize);
return (): void => window.removeEventListener('resize', handleResize);
}, []); // Empty array ensures that effect is only run on mount
return windowDimensions;
};
export default useWindowDimensions;
用法:
import { useWindowDimensions } from '@hooks/useWindowDimensions';
...
const { width, height } = useWindowDimensions();