尝试访问 Remix 中的环境变量时未定义窗口

我正在尝试使用 Remix 将一些环境变量导入浏览器,我一直在关注这个:

https://remix.run/docs/en/v1/guides/envvars

我完全按照步骤 1 和 2 进行操作,但是我无法从浏览器访问 window.ENV 。我收到此错误: ReferenceError: window is not defined

这是我非常简单的组件:

function Test() {
  console.log('Window: ', window);
  return <div>Hello, Test</div>;
}

export default Test;

如果我注释掉 console.log ,我可以在 <script> 中看到带有 <body> 内容的文档顶部的 window.ENV = {...} 。但是取消注释 console.log 向我显示错误消息并且没有 <script> 标记。这告诉我问题在于从文档中设置 window.ENV 而不是从我的组件中。

任何想法将不胜感激!

stack overflow window is not defined when trying to access environment variables in Remix
原文答案
author avatar

接受的答案

您不能在该代码(组件渲染)中访问 window 对象,因为它既在服务器上运行(因为服务器端渲染)也在客户端上运行(就像常规客户端 React 应用程序一样)。并且在服务器上没有 window 对象或任何其他浏览器 API。因此,您需要以这种方式编写该代码,以便它可以在服务器和客户端上运行。

不过,您以后仍然可以使用 window 对象,例如在 useEffect 或某些 onClick 处理程序中,因为此代码只会在客户端运行:

// both this cases will work fine
  useEffect(() => {
    console.log(window.ENV);
  }, []);

// ...

      <button
        onClick={() => {
          console.log(window.ENV);
        }}
      >
        Log env
      </button>

但有时你需要那些 env 值,直接在 render 方法中。您可以将 loader 函数与 useLoaderData 钩子结合使用,如下所示:

export function loader() {
  // process.env is available here because loader runs only on the server side
  return {
    SOME_SECRET: process.env.SOME_SECRET
  };
}

export default function Index() {
  const data = useLoaderData();

  // here you can use everything that you returned from the loader function
  console.log(data.SOME_SECRET);

  return <div>{/* ... */}</div>
}

答案:

作者头像

在REACT中的组件级范围下,无法访问DOM,其中可见窗口对象,而无需使用使用效果挂钩调用,然后从那里引用窗口。

作者头像

使用 Remix 文档建议的 window.ENV 模式的替代方法是在您的 Remix routes/root.tsx 文件中创建一个根 React 上下文,该文件在上下文的 value 属性中包含您的客户端环境变量。然后根据需要通过相应的 useRootContext 钩子访问变量,而无需先使用 useEffect 。下面是一个使用 TypeScript 的简化示例:

上下文/根上下文.ts

import { createContext, useContext } from 'react'

export const RootContext = createContext({
    stripePublicKey: '',
})

export const useRootContext = () => useContext(RootContext)

路由/root.tsx

import { useLoaderData, type LoaderFunction, json } from 'remix'
import { RootContext } from '~/context'

export const loader: LoaderFunction = async () => {
    return json({
        ENV: {
            stripePublicKey: process.env.STRIPE_PUBLIC_KEY,
        },
    })
}

const App = () => {
    const { ENV } = useLoaderData()

    return (
        <html lang="en">
            <body>
                <RootContext.Provider
                    value={{
                        stripePublicKey: ENV.stripePublicKey,
                    }}
                >
                    {/* app markup */}
                </RootContext.Provider>
            </body>
        </html>
    )
}

export default App

通过 useRootContext 访问

import { useRootContext } from '~/context'

const BillingLayout = () => {
    const { stripePublicKey } = useRootContext()

    console.log(stripePublicKey)

    return (<Outlet />)
}