在Next.js React应用程序中,窗口未定义

279

在我使用Next.js应用时,似乎无法访问window

未处理的拒绝(ReferenceError):window未定义

componentWillMount() {
    console.log('window.innerHeight', window.innerHeight);
}

Enter image description here


2
将代码移动到componentDidMount()中,该方法仅在客户端执行,因此可以使用window。此外,componentWillMount()在v17中已被弃用。https://github.com/zeit/next.js/wiki/FAQ#i-use-a-library-which-throws-window-is-undefined - Alexander Staroselsky
28个回答

8

我需要从URL中访问哈希值,因此我想到了这个方法:

const hash = global.window && window.location.hash;

迄今为止最简洁的解决方案 - sidonaldson
这个解决方案也可以工作,你也可以在useEffect()内部定义它。 - Ismoil Shokirov

5

这是我使用的一个易于操作的解决方法。

const runOnClient = (func: () => any) => {
  if (typeof window !== "undefined") {
    if (window.document.readyState == "loading") {
      window.addEventListener("load", func);
    } else {
      func();
    }
  }
};

使用方法:

runOnClient(() => {
// access window as you like
})

// or async
runOnClient(async () => {
// remember to catch errors that might be raised in promises, and use the `await` keyword wherever needed
})

这比仅仅检查typeof window !== "undefined"要好,因为如果你只检查window是否未定义,它不会在页面被重定向时工作,它仅在加载一次期间工作。但是此解决方法即使页面被重定向到也能正常工作,而不仅仅是在加载一次期间工作。


每次都会覆盖 window.onload。 - OwnageIsMagic
@OwnageIsMagic 不是的,如果页面已经加载,它只会设置 window.onload - 524F4A
考虑以下代码片段 runOnClient(f1); runOnClient(f2) - OwnageIsMagic
请阅读以下有关编程的内容:也可参考此链接了解更多信息 https://dev59.com/pXE95IYBdhLWcg3wMrAB - OwnageIsMagic
@OwnageIsMagic 我已经让它附加监听器了,希望现在已经修复了。 - 524F4A

4

我将通用解决方案 (if (typeof window === 'undefined') return;) 封装在一个自定义 hook 中,非常满意。它具有与 React 的 useMemo hook 类似的接口,我非常喜欢。

import { useEffect, useMemo, useState } from "react";

const InitialState = Symbol("initial");

/**
 *
 * @param clientFactory Factory function similiar to `useMemo`. However, this function is only ever called on the client and will transform any returned promises into their resolved values.
 * @param deps Factory function dependencies, just like in `useMemo`.
 * @param serverFactory Factory function that may be called server side. Unlike the `clientFactory` function a resulting `Promise` will not be resolved, and will continue to be returned while the `clientFactory` is pending.
 */
export function useClientSideMemo<T = any, K = T>(
  clientFactory: () => T | Promise<T>,
  deps: Parameters<typeof useMemo>["1"],
  serverFactory?: () => K
) {
  const [memoized, setMemoized] = useState<T | typeof InitialState>(
    InitialState
  );

  useEffect(() => {
    (async () => {
      setMemoized(await clientFactory());
    })();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, deps);

  return typeof window === "undefined" || memoized === InitialState
    ? serverFactory?.()
    : memoized;
}

使用示例:

我正在使用它来动态导入在next.js中与SSR不兼容的库,因为其自身的动态导入仅与组件兼容。

  const renderer = useClientSideMemo(
    async () =>
      (await import("@/components/table/renderers/HighlightTextRenderer"))
        .HighlightTextRendererAlias,
    [],
    () => "text"
  );

正如您所看到的,我甚至实现了一个回退工厂回调,因此您可以在最初在服务器上呈现时提供结果。在所有其他方面,这个钩子应该表现得类似于React的useMemo钩子。欢迎反馈。


3

当我刷新页面时(由于导入与SSR不兼容),我遇到了同样的问题。

对我有用的解决方法是进入出现此问题的页面并强制将导入设置为动态:

import dynamic from 'next/dynamic';


const SomeComponent = dynamic(()=>{return import('../Components/SomeComponent')}, {ssr: false});

//import SomeComponent from '../Components/SomeComponent'

将原始的导入注释掉并动态导入组件会强制客户端渲染组件。
Nextjs 的文档中介绍了动态导入的用法,请参考: https://nextjs.org/docs/advanced-features/dynamic-import 我通过观看以下 YouTube 视频得出了这个解决方案: https://www.youtube.com/watch?v=DA0ie1RPP6g

3

在我开发next.js网站应用时,我遇到了同样的问题。 解决方法是,在生命周期方法或React Hook中引用window对象。例如,假设我想使用Redux创建存储变量,并在其中使用window对象,可以按照以下方式进行:

let store
useEffect(()=>{
    store = createStore(rootReducers,   window.__REDUX_DEVTOOLS_EXTENSION__ && 
    window.__REDUX_DEVTOOLS_EXTENSION__())
 }, [])
 ....

基本上,当您使用Windows对象时,总是使用钩子或componentDidMount()生命周期方法


2

Next JS 13 使用页面路由器

以下是一个代码片段,展示了如何在 localStorage 中创建、更新(也可以使用 create 函数进行更新)和删除一个键(这里是 username


const [username, setUsername] = useState<null | string>(null);

useEffect(() => {
  if (typeof window !== "undefined" && window.localStorage) {
    let token = localStorage.getItem("username");
    setUsername(token);
  }
}, [setUsername]);

const createItem = (newUsername: string) => {
  if (typeof window !== "undefined" && window.localStorage) {
    localStorage.setItem("username", newUsername);

    let token = localStorage.getItem("username");

    setUsername(token);
  }
};

const removeItem = () => {
  if (typeof window !== "undefined" && window.localStorage) {
    localStorage.removeItem("username");
    setUsername(null);
  }
};

console.log(username, "username");

参考资料


1

对于 Next.js 版本 12.1.0,我发现我们可以使用 process.title 来确定我们是在 浏览器 还是在 Node 端。希望这有所帮助!

export default function Projects(props) {
    console.log({ 'process?.title': process?.title });

    return (
        <div></div>
    );
}

1. 从终端,我收到了{ 'process?.title': 'node' }

process.title === 'node'

2. 从Chrome开发工具,我收到了{ 'process?.title': 'browser' }

process.title === 'browser'


1
您可以尝试以下代码片段,用于以下情况 - 获取当前路径名(CurrentUrl Path)
 import { useRouter } from "next/router";

 const navigator = useRouter()
 console.log(navigator.pathname);

1
对于这种情况,Next.js有动态导入
如果一个模块包含了只能在浏览器中使用的库,建议使用动态导入。参考

1

日期:2021年6月8日

检查窗口对象是否存在,然后跟随其中的代码。

 function getSelectedAddress() {
    if (typeof window === 'undefined') return;

    // Some other logic
 }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接