Next.JS React应用程序中未定义窗口

在我的下一个.JS应用程序中,似乎无法访问window

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

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

Enter image description here

333495 次浏览

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

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

在next.JS中,componentDidMount()仅在window和其他浏览器特定API可用的客户端上执行。从next.JS维基

Next.JS是通用的,这意味着它首先在服务器端执行代码, 然后是客户端。窗口对象只存在于客户端,因此如果 你绝对需要在一些反应组件中访问它,你 应该将该代码放在ComponentDidMount中。此生命周期方法将 只能在客户端上执行。您可能还需要检查是否存在 不是一些可能适合你需要的替代通用图书馆。

沿着相同的路线,componentWillMount()将__REACT的V17中的ABC1,因此在不久的将来使用它实际上将是潜在不安全的。

__,ABC0生命周期挂钩在服务器和客户端都可以工作。在您的案例中,服务器在页面服务期间不知道windowdocument,建议将代码移动到其中任何一个

解决方案1:

componentDidMount()

或者,解决方案2

如果它是你只想执行的东西,那么你可以这样写:

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

̶ A ̶ n ̶ o ̶ t ̶ h ̶ e ̶ r ̶ ̶ s ̶ o ̶ l ̶ u ̶ t ̶ i ̶ o ̶ n ̶ ̶ i ̶ s ̶ ̶ b ̶ y ̶ ̶ u ̶ s ̶ i ̶ n ̶ g ̶ ̶ _ _ ABC0 ̶ ̶ t ̶ o ̶ ̶ j ̶ u ̶ s ̶ t ̶ ̶ e ̶ x ̶ e ̶ c ̶ u ̶ t ̶ e̶ ̶ ̶ y ̶ o ̶ u ̶ r ̶ ̶ c ̶ o ̶ m ̶ m ̶ a ̶ n ̶ d ̶ ̶ d ̶ u ̶ r ̶ i ̶ n ̶ g ̶ ̶ r ̶ e ̶ n ̶ d ̶ e ̶ r ̶ i ̶ n ̶ g ̶ ̶ o ̶ n ̶ ̶ t ̶ h ̶ e ̶ ̶ c ̶ l ̶ i ̶ e ̶ n ̶ t ̶ ̶ s ̶I d e o n l y.

但__abc0对象在webpack5和nextjs中已被弃用,因为它只是后端的nodejs变量。

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

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

另一种解决方案是通过使用REACT钩子来代替componentDidMount

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

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

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

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 = {}
}
}

这将避免错误(在我的例子中,错误将在我单击页面的重新加载后发生)。

如果使用反应钩子,则可以将代码移动到效果挂钩中:

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

我在next.JS中开发Web应用程序时也遇到了同样的问题。,这解决了我的问题,你必须在生命周期方法或反应钩子中引用窗口对象。例如,假设我想用Redux创建一个存储变量,在这个存储中,我想使用一个Windows对象,我可以这样做:

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

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

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

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

对于这种情况,next.JS具有动态导入

一个模块包含一个只能在浏览器中工作的库,建议使用动态导入。参考

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

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

日期:2021年8月6日

检查窗口对象是否存在,然后按照代码进行操作。

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


// Some other logic
}

您可以定义一个状态变量并使用窗口事件句柄来处理更改,如下所示。

const [height, setHeight] = useState();


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

下面是我做的一个易于使用的解决方法。

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"要好,因为如果您只是检查窗口是否未定义,如果您的页面被重定向到,它将不会工作,它只在加载时工作一次。但即使页面在加载时被重定向到(而不是一次),这种解决方法也是有效的。

我想把这个有趣的方法留给未来的研究人员。它使用了一个自定义的钩子使用事件侦听器,可以用于许多其他需求。

请注意,您需要在最初发布的版本中应用一些更改,就像我建议的那样,在这里

所以它会像这样结束:

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


有史以来

最好的解决方案

import dynamic from 'next/dynamic';


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

如果是NextJS APP,且内部__为ABC0,则使用以下方式:

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

有点晚,但您也可以考虑使用next动态导入,关闭该组件的SSR

您可以在动态函数中扭曲组件的导入,然后使用返回值作为实际组件。

import dynamic from 'next/dynamic'


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


<>
<BoardDynamic />
</>

global?.window && window.innerHeight

请务必使用操作符__abc0,否则构建命令可能会崩溃。

对于Next.JS版本12.1.0,我发现我们可以使用process.title来确定我们是在browser侧还是在node侧。希望能有所帮助!

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


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

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

process.title === 'node'

2.来自Chrome DevTool,I备用{ 'process?.title': 'browser' }

process.title === 'browser'

我在刷新页面时也遇到了同样的问题(由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

您可以尝试以下代码片段的用例,如-获取当前路径名(currentURL路径)

 import { useRouter } from "next/router";


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

我将通用解决方案(if (typeof window === 'undefined') return;)包装在一个自定义钩子中,我对此非常满意。它有一个类似于Reacts的接口,__我非常喜欢的ABC1钩子。

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"
);

正如您所看到的,我甚至实现了一个回退工厂回调,因此您也可以在最初在服务器上呈现时提供结果。在所有其他方面,该钩子的行为应类似于反作用useMemo钩子。乐于接受反馈。

对于不能使用钩子的人(例如,函数组件):

使用setTimeout(() => yourFunctionWithWindow());将允许它获取窗口实例。我想它只是需要多一点时间来加载。