如何解决Gatsby构建window undefined及React Hook条件调用报错
问题修复方案
你遇到的两个报错根本原因是违反了React Hook的调用规则:所有Hook必须在组件的最顶层调用,不能包裹在条件判断语句中。你将useWindowSize和useEffect放在if (typeof window !== "undefined")的条件分支里,构建阶段和客户端渲染时Hook的调用顺序不一致,就触发了该错误。
Gatsby构建阶段只会执行组件的同步渲染逻辑,不会执行useEffect的回调,所有涉及window、document的浏览器API操作都放到useEffect回调中执行,天然就避开了构建阶段不存在window的问题,不需要在Hook调用层加条件判断。
修正后的完整代码如下:
import * as React from "react" //Library import { useEffect, useState } from "react" import { useStaticQuery, graphql } from "gatsby" import loadable from "@loadable/component" //Components import { Header } from "./header" import { SkipNavContent, SkipNavLink } from "./skip-nav" import { Seo } from "./seo" import Toast from "./toast" //Utils import { StoreContext } from "../context/store-context" import { isBrowser, useWindowSize } from "../utils/helpers" const CartSidebar = loadable(() => import("./cart/CartSidebar")) const Newsletter = loadable(() => import("./newsletter")) const Footer = loadable(() => import("./footer")) export function Layout({ children }) { // 所有Hook放在组件最顶层,符合调用规则 const { loading, didJustAddToCart } = React.useContext(StoreContext) const [showLazySections, setShowLazySections] = React.useState(false) const [headerHeight, setHeaderHeight] = useState(null) // 直接调用useWindowSize,不要包条件,Hook内部兼容SSR返回默认值即可 const { height: windowHeight } = useWindowSize() // 补上你遗漏的siteTitle静态查询 const { site } = useStaticQuery(graphql` query SiteTitleQuery { site { siteMetadata { title } } } `) const siteTitle = site.siteMetadata.title // 动态设置vh变量,useEffect仅在客户端执行,天然避开构建阶段window问题 useEffect(() => { if (isBrowser) { document.body.style.setProperty("--vh", `${windowHeight * 0.01}px`) } }, [windowHeight]) useEffect(() => { setShowLazySections(true) }, []) return ( <div style={headerHeight ? { "--headerHeight": `${headerHeight}px` } : null} > <SkipNavLink /> <Seo /> <Header onSetup={({ height }) => setHeaderHeight(height)} /> <SkipNavContent>{children}</SkipNavContent> {showLazySections && ( <> <CartSidebar /> <Newsletter /> <Footer siteTitle={siteTitle} /> </> )} </div> ) }
如果你的useWindowSize还没有做SSR兼容,内部直接引用window导致构建报错,可以修改为如下兼容写法:
// utils/helpers.js 中的useWindowSize调整为 export function useWindowSize() { const [windowSize, setWindowSize] = useState({ width: 0, height: 0, }); useEffect(() => { function handleResize() { setWindowSize({ width: window.innerWidth, height: window.innerHeight, }); } window.addEventListener("resize", handleResize); handleResize(); return () => window.removeEventListener("resize", handleResize); }, []); return windowSize; }
该写法服务端渲染时默认返回宽高为0,客户端挂载后自动更新为真实值,不会触发构建报错。
内容的提问来源于stack exchange,提问作者user10229330
相关产品推荐
相关产品推荐

