如何解决 React 和 Next.js 中的“窗口未定义”错误
Next.js 是一个具有预渲染功能的 React 框架。这意味着对于每个页面,Next.js 都会尝试生成该页面的 HTML,以获得更好的 SEO 和性能。
这就是为什么,如果你想这样做:
// components/Scroll.js
window.addEventListener("scroll", function() {
console.log("scroll!")
});
然后它将失败并显示“ReferenceError:窗口未定义”:
因为在Node.js的世界里,window是没有定义的,window只在浏览器中可用。
有三种方法可以解决这个问题:
1. 第一个解决方案:typeof
虽然您不能使用:
if (window !== undefined) {
// browser code
}
因为这会尝试将不存在的变量(window)与 undefined 进行比较,从而导致严重的“ReferenceError: window is notdefined”。你仍然可以使用:
if (typeof window !== "undefined") {
// browser code
}
因为 typeof 不会尝试评估“window”,它只会尝试获取其类型,在我们的 Node.js 例子中为:“undefined”。
附言:感谢
Rogier Nitschelm提醒我这一点。我最初尝试过,if (typeof window !== undefined)
但由于前面提到的原因,最终失败了。
下面的其他解决方案更加奇特,但仍然值得。
2. 第二种解决方案:useEffect hook
解决这个问题的“React”方法是使用useEffect React hook。它只在渲染阶段运行,因此不会在服务器上运行。
让我们更新我们的 scroll.js 组件:
// components/Scroll.js
import React, { useEffect } from "react";
export default function Scroll() {
useEffect(function mount() {
function onScroll() {
console.log("scroll!");
}
window.addEventListener("scroll", onScroll);
return function unMount() {
window.removeEventListener("scroll", onScroll);
};
});
return null;
}
我们在这里所做的是将我们的初始 JavaScript 文件转换为真正的 React 组件,然后需要通过以下方式将其添加到您的 React 树中:
// pages/index.js
import Scroll from "../components/Scroll";
export default function Home() {
return (
<div style={{ minHeight: "1000px" }}>
<h1>Home</h1>
<Scroll />
</div>
);
}
提示:示例中我们使用 useEffect 的方式是在挂载/卸载时注册和注销监听器。但您也可以只在挂载时注册,并忽略其他渲染事件,具体操作如下:
// components/Scroll.js
import React, { useEffect } from "react";
export default function Scroll() {
useEffect(function onFirstMount() {
function onScroll() {
console.log("scroll!");
}
window.addEventListener("scroll", onScroll);
}, []); // empty dependencies array means "run this once on first mount"
return null;
}
3.第三种方案:动态加载
另一种解决方案是使用动态导入和选项来加载你的 Scroll 组件srr: false
。这样,你的组件根本不会在服务器端渲染。
当您导入依赖于 的外部模块时,此解决方案特别有效window
。(感谢Justin!)
// components/Scroll.js
function onScroll() {
console.log("scroll!");
}
window.addEventListener("scroll", onScroll);
export default function Scroll() {
return null;
}
// pages/index.js
import dynamic from "next/dynamic";
const Scroll = dynamic(
() => {
return import("../components/Scroll");
},
{ ssr: false }
);
export default function Home() {
return (
<div style={{ minHeight: "1000px" }}>
<h1>Home</h1>
<Scroll />
</div>
);
}
如果您不需要 useEffect 的功能,您甚至可以完全删除它的使用,如下所示。
最后,如果您想要全局加载一个组件并忘记它(页面更改时不再挂载/卸载),那么您也可以仅在_app.jsScroll
中加载您的组件。
在本文中,我使用了这种技术通过NProgress显示顶级进度条:
