【问题标题】:Window is not defined in Next.js React appNext.js React 应用程序中未定义窗口
【发布时间】:2021-10-26 12:35:27
【问题描述】:

在我的 Next.js 应用程序中,我似乎无法访问 window

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

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

【问题讨论】:

  • 将代码移动到componentDidMount(),它只在window可用的客户端上执行。此外,componentWillMount() 在 v17 中是 deprecated https://github.com/zeit/next.js/wiki/FAQ#i-use-a-library-which-throws-window-is-undefined

标签: javascript reactjs next.js


【解决方案1】:

另一种解决方案是通过使用̶p̶r̶o̶c̶e̶s̶s̶.̶b̶r̶o̶w̶s̶e̶r̶只执行̶您的命令在渲染过程中在客户端仅。 P>

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

所以我们必须使用浏览器返回的window 对象。

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

其他解决方案是使用 react hook 替换 componentDidMount:

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

【讨论】:

  • 你的意思是typeof window !== "undefined",否则就是服务端运行
  • 说真的,那家伙需要编辑他的评论
  • @Robbie - 我让另一个“家伙”来编辑评论。
  • 最佳答案,看看这个。
【解决方案2】:

将代码从componentWillMount()移动到componentDidMount()

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

在 Next.js 中,componentDidMount() 仅在 window 和其他浏览器特定 API 可用的客户端上执行。来自Next.js wiki

Next.js 是通用的,这意味着它首先在服务器端执行代码, 然后是客户端。窗口对象只存在于客户端,所以如果 你绝对需要在一些 React 组件中访问它,你 应该将该代码放在 componentDidMount 中。这种生命周期方法将 只能在客户端执行。您可能还想检查是否有 不是可以满足您需求的替代通用库。

同样,componentWillMount() 在 React v17 中将是 deprecated,因此在不久的将来使用它实际上可能不安全。

【讨论】:

    【解决方案3】:

    如果您使用 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

    【讨论】:

    • 这似乎是在反应功能组件中正确执行此操作的唯一方法。
    • 这可行,但如果您在 useEffect 中执行更新状态的操作,请确保添加一个空的 [] 作为 deps。
    【解决方案4】:

    没有 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
    

    【讨论】:

    • 即使库在导入时访问“窗口”,这个也可以工作
    • @PeterMortensen,SSR = 服务器端渲染
    • 如何从外部js访问变量?比如 var myVar = "bla"
    • @bl4ckck 不幸的是,您只能对组件(而不是函数)使用动态导入
    • TypeError: Cannot call a class as a function
    【解决方案5】:

    componentWillMount() 生命周期钩子在服务器端和客户端都有效。在您的情况下,服务器在页面服务期间不知道 windowdocument,建议将代码移至任一

    解决方案 1:

    componentDidMount()
    

    或者,解决方案 2

    如果这是您只想执行的操作,那么您可以编写如下内容:

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

    【讨论】:

      【解决方案6】:

      在你的类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 = {}
              }
      }
      

      这将避免错误(在我的情况下,在我单击重新加载页面后会发生错误)。

      【讨论】:

      • global 上是否有任何文档?
      • 我强烈建议不要使用这种方法。在服务器端设置global.window 的行为与浏览器环境中提供的实际window 对象不同。
      【解决方案7】:

      我必须从 URL 访问哈希,所以我想出了这个

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

      【讨论】:

      • 迄今为止最简洁的解决方案
      • 这个解决方案也可以,你也可以在 useEffect() 中定义它
      【解决方案8】:

      我在 next.js 中开发 Web 应用程序时遇到了同样的问题 这解决了我的问题,您必须在生命周期方法或 react Hook 中引用 window 对象.例如,假设我想用 redux 创建一个存储变量,并且在这个存储中我想使用一个 windows 对象,我可以这样做:

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

      所以基本上,当您使用窗口的对象时,总是使用钩子来玩转或componentDidMount() 生命周期方法

      【讨论】:

        【解决方案9】:

        发生错误是因为窗口尚不可用,而组件仍在安装中。组件挂载后可以访问窗口对象。

        您可以创建一个非常有用的钩子来获取动态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();
        

        【讨论】:

        • 聪明。非常好。
        • 每次调整窗口大小时都会起作用,在您使用 css 时很有用 -> 让您的页面响应式
        【解决方案10】:

        对于这种情况,Next.js 有Dynamic Import

        包含仅在浏览器中工作的库的模块,建议使用动态导入。 Refer

        【讨论】:

          【解决方案11】:

          有史以来最好的解决方案

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

          【讨论】:

          • 您的答案可以通过额外的支持信息得到改进。请edit 添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。你可以找到更多关于如何写好答案的信息in the help center
          【解决方案12】:

          日期:06/08/2021

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

           function getSelectedAddress() {
              if (typeof window === 'undefined') return;
          
              // Some other logic
           }
          

          【讨论】:

            【解决方案13】:

            您可以定义一个状态变量并使用窗口事件句柄来处理这样的变化。

            const [height, setHeight] = useState();
            
            useEffect(() => {
                if (!height) setHeight(window.innerHeight - 140);
                window.addEventListener("resize", () => {
                    setHeight(window.innerHeight - 140);
                });
            }, []);
            

            【讨论】:

              【解决方案14】:

              这是我所做的一个易于使用的解决方法。

              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.onload 每次
              • @OwnageIsMagic 不,如果你能看到它设置window.onload 仅当页面未加载。
              • 考虑这个sn-p runOnClient(f1); runOnClient(f2)
              • @OwnageIsMagic 我让它附加了监听器,我希望它现在已经修复了。
              【解决方案15】:

              如果是 NextJS 应用并且在 _document.js 内,使用如下:

              <script dangerouslySetInnerHTML={{
                      __html: `
                          var innerHeight = window.innerHeight;
                      `
                      }} />
              

              【讨论】:

                【解决方案16】:

                我想保留这种对未来研究人员感兴趣的方法。它使用了一个自定义钩子useEventListener,可以用于许多其他需求。

                请注意,您需要对最初发布的内容进行一些更改,例如我建议的here

                所以它会像这样结束:

                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])
                }
                
                

                【讨论】:

                  【解决方案17】:

                  我使用如下:

                  const ISSERVER = typeof window === "undefined";
                  if (!ISSERVER) localStorage.setItem(key, value);
                  

                  它对我有用。

                  【讨论】:

                    猜你喜欢
                    • 2021-10-06
                    • 2022-01-18
                    • 2021-12-04
                    • 2021-09-19
                    • 1970-01-01
                    • 2020-05-06
                    • 1970-01-01
                    相关资源
                    最近更新 更多