参考错误:window undefined - 开发人员指南

您是否曾经在控制台中看到过此错误并想知道发生了什么?您并不孤单!臭名昭著的“窗口未定义”错误是使用 React、Next.js 或任何服务器端渲染 (SSR) 应用程序的开发人员最常见的麻烦之一。

这个错误是怎么回事?🤔

首先,让我们了解“window”到底是什么。在基于浏览器的 JavaScript 中,“window”是一个代表浏览器窗口的全局对象。它包含各种有用的东西,例如:

  • window.localStorage 用于存储数据
  • window.location 获取 URL 信息
  • window.document 用于 DOM 操作
  • 还有更多特定于浏览器的 API
  • 问题是什么?这个对象只存在于浏览器中。当你的代码在服务器上运行时(比如在 SSR 期间),没有浏览器,因此也没有 `window` 对象!

    Server vs Browser Environment

    发生此错误的常见情况

  • 直接窗口访问
  • 当您尝试直接在组件中访问窗口属性时(尤其是在初始渲染期间),您会遇到此错误。这通常在检查屏幕尺寸或浏览器功能时发生:

    // This will break during SSR
    const screenWidth = window.innerWidth;
  • 第三方库
  • 许多浏览器专用库都假设它们在客户端环境中运行。当这些库尝试在服务器端渲染期间访问窗口时,您的应用程序将崩溃:

    // Some libraries assume window exists
    import someLibrary from 'browser-only-library';
  • localStorage 使用情况
  • localStorage 是客户端存储中经常访问的窗口属性。在服务器渲染期间尝试使用它将触发错误:

    // This will fail on the server
    const savedData = localStorage.getItem('user-data');

    如何修复?💪

    1. 使用 useEffect Hook

    最直接的解决方案是将特定于浏览器的代码包装在 `useEffect` 钩子中:

    import { useEffect } from 'react';
    
    function MyComponent() {
        useEffect(() => {
            // Safe to use window here
            const screenWidth = window.innerWidth;
            console.log('Screen width:', screenWidth);
        }, []);
        return 
    My Component
    ; }

    2. 检查窗口是否已定义

    创建一个实用函数来安全地检查窗口:

    const isClient = typeof window !== 'undefined';
    
    function MyComponent() {
        if (isClient) {
        // Safe to use window here
        }
        return 
    My Component
    ; }

    3. 动态导入(Next.js 解决方案)

    对于 Next.js 应用程序,使用带有 `ssr: false` 的动态导入:

    import dynamic from 'next/dynamic';
    
    const BrowserOnlyComponent = dynamic(
        () => import('../components/BrowserComponent'),
        { ssr: false }
    );

    专业提示🌟

    使用这些久经考验的模式避免“窗口未定义”错误:

  • 创建自定义钩子
  • function useWindowSize() {
        const [size, setSize] = useState({ width: 0, height: 0 });
        useEffect(() => {
            const updateSize = () => {
                setSize({
                    width: window.innerWidth,
                    height: window.innerHeight
                 });
            };
    
            window.addEventListener('resize', updateSize);
            updateSize();
            return () => window.removeEventListener('resize', updateSize);
        }, []);
        return size;
    }
  • 优雅地处理 SSR
  • function MyComponent() {
        const [isMounted, setIsMounted] = useState(false);
        useEffect(() => {
            setIsMounted(true);
        }, []);
    
        if (!isMounted) {
            return null; // or a loading state
        }
        return 
    Browser-only content
    ; }

    需要注意的常见问题⚠️

  • 忘记 SSR:永远记住你的 React 代码可能会首先在服务器上运行,从而导致“窗口未定义”错误。
  • 第三方依赖项:某些软件包假定它们在浏览器中运行。使用它们之前,请检查它们的 SSR 兼容性。
  • 条件导入:除非必要,否则不要使用动态导入 - 它们会影响性能。
  • 测试你的代码

    请记住在服务器和客户端环境中测试您的应用程序。以下是一个简单的测试设置:

    describe('MyComponent', () => {
        it('handles server-side rendering', () => {
        // Test SSR scenario
        const { container } = render();
        expect(container).toBeInTheDocument();
    });
    
    it('handles client-side rendering', () => {
        // Mock window object
        const { container } = render();
        expect(container).toBeInTheDocument();
        });
    });

    结论

    “窗口未定义”错误乍一看可能很吓人,但一旦您了解了它发生的原因,处理起来其实很简单。请记住:

  • 使用 useEffect 编写特定于浏览器的代码
  • 使用前检查窗口是否存在
  • 考虑对仅限浏览器的组件使用 Next.js 动态导入
  • 始终测试服务器和客户端场景
  • 如果您想了解有关窗口对象的更多信息,可以阅读 MDN Web Docs。

    Success gif

    祝你编码愉快!🚀