在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个回答

242

另一种解决方案是使用process.browser来仅在客户端渲染期间执行您的命令。

但是,process对象已在Webpack5和NextJS中被弃用,因为它是一个仅针对后端的NodeJS变量。

因此,我们必须使用浏览器的window对象。

if (typeof window !== "undefined") {
  // Client-side-only code
}

另一种解决方案是使用 React Hook 替换 componentDidMount

useEffect(() => {
    // Client-side-only code
})


2
这种方法现在已经被弃用了。https://github.com/zeit/next.js/issues/5354#issuecomment-520305040 - Anton Dozortsev
30
你的意思是 typeof window !== "undefined",否则它就是在服务器端运行。 - Allwe
6
认真地说,那个人需要编辑他的评论。 - Robbie Cook
13
@Robbie - 我请另一个人编辑了那条评论。 - Guy
1
我整天都在开发一个React网站。这是我的第一个项目,我一整天都卡在这个问题上了。谢谢。 - Bender
显示剩余3条评论

107

如果你使用React Hooks,你可以将代码移动到Effect Hook中:

import * as React from "react";

export const MyComp = () => {

  React.useEffect(() => {
    // window is accessible here.
    console.log("window.innerHeight", window.innerHeight);
  }, []);

  return (<div></div>)
}

useEffect 中的代码仅在客户端(浏览器)执行,因此它可以访问 window


1
这似乎是在React函数组件中正确执行的唯一方法。 - Mahmoud Mousa
9
如果你在 useEffect 内部进行会更新 state 的操作,尽管这会起作用,但请确保将空的 [] 添加为依赖项。 - deowk

99
将代码从 componentWillMount() 移动到 componentDidMount():
componentDidMount() {
  console.log('window.innerHeight', window.innerHeight);
}
在Next.js中,componentDidMount()仅在客户端执行,因为只有在客户端才能使用window和其他特定于浏览器的API。来自Next.js wiki的说明如下:
Next.js是通用的,这意味着它首先在服务器端执行代码,然后在客户端执行。 window对象仅在客户端存在,因此,如果您绝对需要在某些React组件中访问它,应将该代码放入componentDidMount中。 这个生命周期方法只会在客户端执行。 您也可能想要检查是否有一些可替代的通用库可以满足您的需求。
同样地,componentWillMount()将在React的第17版中被弃用,因此在不久的将来使用它可能会不安全。

84

没有SSR

https://nextjs.org/docs/advanced-features/dynamic-import#with-no-ssr

import dynamic from 'next/dynamic'

const DynamicComponentWithNoSSR = dynamic(
  () => import('../components/hello3'),
  { ssr: false }
)

function Home() {
  return (
    <div>
      <Header />
      <DynamicComponentWithNoSSR />
      <p>HOME PAGE is here!</p>
    </div>
  )
}

export default Home

3
即使库正在访问“window”,此方法仍然可用于导入。 - Pedro Andrade
@PeterMortensen,SSR = 服务器端渲染 - Kate
1
如何从外部JS访问变量?例如:var myVar = "bla" - bl4ckck
2
@bl4ckck 很遗憾,你只能在组件上使用动态导入(而不能在函数上使用)。 - Mr Washington
TypeError: Cannot call a class as a function - Shahriar

28

出现错误是因为在组件加载时,窗口还不可用。只有在组件挂载后才能访问窗口对象。

您可以创建一个非常有用的钩子来获取动态的window.innerHeightwindow.innerWidth

const useDeviceSize = () => {

  const [width, setWidth] = useState(0)
  const [height, setHeight] = useState(0)

  const handleWindowResize = () => {
    setWidth(window.innerWidth);
    setHeight(window.innerHeight);
  }

  useEffect(() => {
    // component is mounted and window is available
    handleWindowResize();
    window.addEventListener('resize', handleWindowResize);
    // unsubscribe from the event on component unmount
    return () => window.removeEventListener('resize', handleWindowResize);
  }, []);

  return [width, height]

}

export default useDeviceSize 

使用案例:

const [width, height] = useDeviceSize();

5
聪明。非常好。 - Bill Haack
1
每次调整窗口大小时都会起作用,这在处理 CSS 时非常有用 -> 可以使您的页面具有响应性。 - Ismoil Shokirov
1
绝妙的解决方案。通用且可在任何地方重复使用。 - vially
@user884321,它将从第一次渲染中正确打印宽度和高度。您可以在此处进行检查:https://codesandbox.io/s/weathered-water-575xy6?file=/src/App.js - undefined
@IsmoilShokirov 请问这是什么链接? - undefined
这个在初始调用时返回0作为窗口宽度/高度,可能会导致意外行为。我在下面提供了一个解决这个问题的答案。 - undefined

16

global?.window && window.innerHeight

使用运算符?.非常重要,否则构建命令可能会崩溃。


2
那在我的React应用中的Hooks里帮了我很多。非常感谢。 - undefined

14

componentWillMount()生命周期钩子函数同时适用于服务器端和客户端。在您的情况下,在页面服务期间,服务器将不知道关于windowdocument的任何信息,建议将代码移至以下位置之一:

解决方案1:

componentDidMount()

或者,解决方案2

如果你只想在某个情况下执行某些操作,可以编写类似以下内容的代码:

componentWillMount() {
    if (typeof window !== 'undefined') {
        console.log('window.innerHeight', window.innerHeight);
    }
}

12

有点晚了,但是你也可以考虑使用来自next动态导入(Dynamic Imports)来关闭该组件的SSR

你可以将该组件的导入封装在一个动态函数中,然后使用返回值作为实际组件。

import dynamic from 'next/dynamic'

const BoardDynamic = dynamic(() => import('../components/Board.tsx'), {
  ssr: false,
})


<>
   <BoardDynamic />
</>

3
请注意,在当前版本的nextJS中,应使用ssr而不是SSR - Barry Michael Doyle

11

Best solution ever

import dynamic from 'next/dynamic';

const Chart = dynamic(()=> import('react-apexcharts'), {
    ssr:false,
})


3
你的回答可以通过提供更多支持信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人可以确认你的答案是正确的。您可以在帮助中心中找到有关如何编写良好答案的更多信息。 - Community
这是否意味着该页面将在浏览器中呈现而不是服务器上?对我来说,使用Next.js的整个目的就是在服务器上进行渲染。 - Shamseer Ahammed
是的,因为在为我们的网站生成静态HTML内容时,我们无法访问任何窗口/屏幕全局对象。 - amerw

10

在你的类的构造函数中,你可以添加Component

if (typeof window === 'undefined') {
    global.window = {}
}

例子:

import React, { Component } from 'react'

class MyClassName extends Component {

    constructor(props){
        super(props)
        ...
        if (typeof window === 'undefined') {
            global.window = {}
        }
}

这样做可以避免错误(在我的情况下,错误会在我点击页面重新加载后出现)。


1
有关 global 的文档吗? - Jayen
3
我强烈建议不要采用这种方法。在服务器端设置 global.window 不会像浏览器环境中提供的实际 window 对象一样运行。 - juliomalves
1
虽然这样可以避免客户端出现错误,但很可能会产生其他你不容易发现或解决的问题。对于未来的开发者,请听从@juliomalves的建议,避免采用这种方法。 - Jason R Stevens CFA

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