在NextJS React应用中未定义窗口?

Leo*_*ban 9 javascript reactjs next.js

在我的NextJS应用中,我似乎无法访问window

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

componentWillMount() {
    console.log('window.innerHeight', window.innerHeight);
}
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

Rot*_*eti 59

如果您使用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>)
}
Run Code Online (Sandbox Code Playgroud)

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

  • 尽管如果您在 useEffect 中执行更新状态的操作,请确保添加一个空的“[]”作为 deps,但这是有效的。 (8认同)

小智 44

无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
Run Code Online (Sandbox Code Playgroud)

  • 即使库在导入时访问“窗口”,这个也可以工作 (3认同)
  • @bl4ckck不幸的是你只能在组件上使用动态导入(而不是函数) (2认同)

Ism*_*rov 29

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

您可以创建一个非常有用的钩子来获得动态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 
Run Code Online (Sandbox Code Playgroud)

使用案例:

const [width, height] = useDeviceSize();
Run Code Online (Sandbox Code Playgroud)

  • 聪明的。很不错。 (5认同)

Ale*_*sky 19

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

componentDidMount() {
  console.log('window.innerHeight', window.innerHeight);
}
Run Code Online (Sandbox Code Playgroud)

在NextJS中,componentDidMount()仅执行客户端window和其他特定于浏览器的API 的客户端。从NextJS Wiki

Next.js是通用的,这意味着它首先在服务器端执行代码,然后在客户端执行代码。window对象仅在客户端存在,因此,如果您绝对需要在某些React组件中访问它,则应将该代码放在componentDidMount中。此生命周期方法将仅在客户端上执行。您可能还需要检查是否没有满足您需求的替代通用库。

同样,React v17 componentWillMount()中将弃用该功能,因此在不久的将来有效地使用它是不安全的。


Dar*_*ert 16

其他解决方案是process.browser仅在客户端渲染期间使用来执行命令。

if (process.browser) {
  // client-side-only code
}
Run Code Online (Sandbox Code Playgroud)

  • 你的意思是`typeof window !== "undefined"`,因为否则它是服务器端运行的 (16认同)
  • @Robbie - 我让另一个“人”来编辑评论。 (14认同)
  • 当前的方法是检查 `if (typeof window === "undefined") { //client side code }` (4认同)
  • 这种方法现在已经被**弃用** https://github.com/zeit/next.js/issues/5354#issuecomment-520305040 (3认同)
  • 说真的,那家伙需要编辑他的评论 (3认同)

小智 16

global?.window && window.innerHeight

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

  • 这对我在 Hooks 中的 React 应用程序有帮助。多谢。 (2认同)

Par*_*avi 12

有点晚了,但您也可以考虑在关闭该组件时使用动态导入nextSSR

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

import dynamic from 'next/dynamic'

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

Run Code Online (Sandbox Code Playgroud)
<>
   <BoardDynamic />
</>
Run Code Online (Sandbox Code Playgroud)

  • 注意,在当前版本的 nextJS 中,它是“ssr”,而不是“SSR” (3认同)

小智 11

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

if (typeof window === 'undefined') {
    global.window = {}
}
Run Code Online (Sandbox Code Playgroud)

例子:

import React, { Component } from 'react'

class MyClassName extends Component {

    constructor(props){
        super(props)
        ...
        if (typeof window === 'undefined') {
            global.window = {}
        }
}
Run Code Online (Sandbox Code Playgroud)

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

  • 我强烈建议不要采用这种方法。在服务器端设置“global.window”的行为与浏览器环境中提供的实际“window”对象的行为不同。 (3认同)

小智 11

有史以来最好的解决方案

import dynamic from 'next/dynamic';

const Chart = dynamic(()=> import('react-apexcharts'), {
    ssr:false,
})
Run Code Online (Sandbox Code Playgroud)

  • 通过额外的支持信息可以改进您的答案。请[编辑]添加更多详细信息,例如引文或文档,以便其他人可以确认您的答案是正确的。您可以[在帮助中心](/help/how-to-answer)找到有关如何写出好的答案的更多信息。 (4认同)

小智 10

componentWillMount()生命周期钩子适用于服务器和客户端。如果服务器不知道页面服务windowdocument在页面服务期间,建议将代码移动到

解决方案1:

componentDidMount()
Run Code Online (Sandbox Code Playgroud)

或者,解决方案 2

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

componentWillMount() {
    if (typeof window !== 'undefined') {
        console.log('window.innerHeight', window.innerHeight);
    }
}
Run Code Online (Sandbox Code Playgroud)


Cha*_*wki 5

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

const hash = global.window && window.location.hash;
Run Code Online (Sandbox Code Playgroud)


Roj*_*Roj 5

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

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

用法:

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
})
Run Code Online (Sandbox Code Playgroud)

这比仅仅更好typeof window !== "undefined",因为如果您只是检查窗口是否未定义,那么如果您的页面被重定向到,它就不会工作,它只会在加载时工作一次。但即使页面被重定向到,此解决方法也有效,而不仅仅是在加载时一次。


Nic*_*mer 5

刷新页面时我遇到了同样的问题(由于导入不能很好地与 SSR 配合使用)。

对我来说解决这个问题的方法是转到发生这种情况的页面并强制导入动态:

import dynamic from 'next/dynamic';


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

//import SomeComponent from '../Components/SomeComponent'
Run Code Online (Sandbox Code Playgroud)

注释掉原始导入并动态导入组件会强制在客户端呈现组件。

Nextjs 的文档中介绍了动态导入: https ://nextjs.org/docs/advanced-features/dynamic-import

我通过观看 YouTube 视频找到了这个解决方案: https://www.youtube.com/watch ?v=DA0ie1RPP6g


tlt*_*tlt 5

我将通用解决方案 ( if (typeof window === 'undefined') return;) 包装在自定义挂钩中,对此我非常满意。它有一个类似的反应useMemo钩子界面,我非常喜欢。

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;
}
Run Code Online (Sandbox Code Playgroud)

使用示例:

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

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

正如您所看到的,我什至实现了后备工厂回调,因此您也可以在最初在服务器上渲染时提供结果。在所有其他方面,这个钩子的行为应该类似于反应useMemo钩子。开放反馈。