在我使用Next.js应用时,似乎无法访问window
:
未处理的拒绝(ReferenceError):window未定义
componentWillMount() {
console.log('window.innerHeight', window.innerHeight);
}
在我使用Next.js应用时,似乎无法访问window
:
未处理的拒绝(ReferenceError):window未定义
componentWillMount() {
console.log('window.innerHeight', window.innerHeight);
}
https://nextjs.org/docs/getting-started/react-essentials#the-use-client-directive
您可以定义一个状态变量,并使用窗口事件处理来处理更改,如下所示。
const [height, setHeight] = useState();
useEffect(() => {
if (!height) setHeight(window.innerHeight - 140);
window.addEventListener("resize", () => {
setHeight(window.innerHeight - 140);
});
}, []);
对于那些无法使用钩子(例如函数组件)的人:
使用 setTimeout(() => yourFunctionWithWindow());
将允许它获取窗口实例。猜测它只需要更多的时间来加载。
这里没有一个答案是完全完整的,大多数人最终会来到这里,因为他们遇到了相同的问题,但是在不同的情况下需要不同的解决方案,所以让我分享一下从我的付费课程中提取的摘要。
原始问题提到了一个旧版本的React,使用回调函数,但是我的答案是基于hooks,并且与当前版本的React(16+)和Next(12+)更相关。
这些库在服务器端导入时会崩溃,因为它们期望window对象立即可用。
你需要我所称之为浏览器组件,这是一个无法在服务器上预渲染的React组件,与Next/React中在服务器上预渲染的客户端组件相反。
解决方案:使用next/dynamic和ssr: false
进行延迟加载来导入你的浏览器组件。现在你的组件只会在浏览器中导入和运行。
这是一个浏览器组件。理想情况下,它应该被重写为客户端组件,但由于您无法控制代码,可能无法做到这一点。
您可以使用NoSsr
组件。您还可以在父组件中使用useMounted hook
(Next文档称之为"useClient"),但前提是父组件是客户端组件。因此,在这种情况下,NoSsr
是更好的选择。
相关的实用代码:
export const useMounted = () => {
const [mounted, setMounted] = useState<boolean>()
// effects run only client-side
// so we can detect when the component is hydrated/mounted
// @see https://react.dev/reference/react/useEffect
useEffect(() => {
setMounted(true)
}, [])
return mounted
}
export function NoSsr({ children }: { children: React.ReactNode }) {
const mounted = useMounted();
if (!mounted) return null;
return <>{children}</>;
}
该组件应该是一个客户端组件。确保在服务器端预渲染时安全。
您可以使用上面的useMounted
钩子重写组件并进行条件渲染:只有在组件挂载时才使用window
对象进行渲染。
这将把您的“浏览器组件”转变为一个“客户端组件”,即使在服务器端不渲染任何内容或加载器,也是安全的。
更广泛地说,这是您想要对DOM进行命令式修改的情况,也是最接近原始问题的情况。
将其变为客户端组件。
您可以将有问题的代码包装在一个效果中,如下所示:useEffect(() => { console.log(window.innerHeight) }, [])
。
在服务器端渲染期间,它将记录/渲染空内容或加载器,但这完全没问题。
'use client'
const MyClientC = () => {
const desiredCalculation = helperFN.bind(this)
...
}
// helperFN.ts
interface Window {
innerHeight: number
...
}
const isBrowser = () => typeof window !== 'undefined';
let Window: Window = {} as Window;
if (typeof window !== 'undefined') {
Window = window;
}
const helperFn = () => {
if (!isBrowser()) {
return false;
}
return Window.innerHeight ...
使用钩子的解决方案。 感谢 @luckgaer。
当在客户端组件中调用时,此钩子在初始(服务器)渲染时将返回null,因此不会抛出上述错误。 我们只需在客户端组件中检查它不是null,并在第一次渲染时跳过使用它。以下是在客户端组件中使用该钩子的示例。
"use client";
import { useLayoutEffect, useState } from "react";
const useWindowWidth = (): number | null => {
if (!global?.window) return null;
const [windowWidth, setWindowWidth] = useState(window.innerWidth);
useLayoutEffect(() => {
let timeoutId: NodeJS.Timeout;
const handleResize = () => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
setWindowWidth(window.innerWidth);
}, 100);
};
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
return windowWidth;
};
export default useWindowWidth;
const windowWidth = useWindowWidth();
if (windowWidth && windowWidth >= 850)...
我想把这个有趣的方法留给未来的研究者。它使用了一个自定义钩子useEventListener,可以在许多其他需求中使用。
请注意,您需要对最初发布的内容进行一些小修改,就像我在这里建议的那样。
所以它会变成这样:
import { useRef, useEffect } from 'react'
export const useEventListener = (eventName, handler, element) => {
const savedHandler = useRef()
useEffect(() => {
savedHandler.current = handler
}, [handler])
useEffect(() => {
element = !element ? window : element
const isSupported = element && element.addEventListener
if (!isSupported) return
const eventListener = (event) => savedHandler.current(event)
element.addEventListener(eventName, eventListener)
return () => {
element.removeEventListener(eventName, eventListener)
}
}, [eventName, element])
}
如果是 NextJS 应用程序并且在 _document.js 中,请使用以下代码:
<script dangerouslySetInnerHTML={{
__html: `
var innerHeight = window.innerHeight;
`
}} />
componentDidMount()
中,该方法仅在客户端执行,因此可以使用window
。此外,componentWillMount()
在v17中已被弃用。https://github.com/zeit/next.js/wiki/FAQ#i-use-a-library-which-throws-window-is-undefined - Alexander Staroselsky