окно не определено при попытке доступа к переменным среды в Remix
Я пытаюсь получить некоторые переменные среды в браузере с помощью Remix, и я следил за этим:
https://remix.run/docs/en/v1/guides/envvars
Я точно выполнил шаги 1 и 2, однако не могу получить доступ из браузера. Я получаю эту ошибку:
ReferenceError: window is not defined
И вот мой действительно простой компонент:
function Test() {
console.log('Window: ', window);
return <div>Hello, Test</div>;
}
export default Test;
Если я закомментирую, я увижу в
<body>
вверху документа с
window.ENV = {...}
содержание. Однако раскомментировать
console.log
показывает мне сообщение об ошибке и нет
<script>
тег. Это говорит мне, что проблема связана с настройкой
window.ENV
из документации, а не из моего компонента.
Любые мысли будут оценены!
5 ответов
Вы не можете получить доступ к объекту в этом коде (рендеринг компонента), потому что он работает как на сервере (из-за рендеринга на стороне сервера), так и на клиенте (так же, как обычное приложение React на стороне клиента). А на сервере нет объекта или каких-то других браузерных 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 сразу, прямо в методе рендеринга. Что вы можете использовать
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>
}
Альтернатива использованиюwindow.ENV
Шаблон, предложенный документацией Remix, заключается в создании корневого контекста React в вашем файле Remix route/root.tsx , который включает ваши переменные среды на стороне клиента в свойстве значения контекста. Затем получите доступ к переменным по мере необходимости через соответствующий хук без необходимости использованияuseEffect
первый. Ниже приведен упрощенный пример с TypeScript:
контекст/root-context.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 />)
}
Использование объекта глобального окна по-прежнему остается жизнеспособным подходом после документации Remix v1.15 по определению переменных среды браузера .
После добавления ENV в окно вам необходимо получить к нему доступ, что можно сделать, избегаяuseEffect
или контекст , проверив, находитесь ли вы на стороне сервера или на стороне клиента в браузере, на основе ошибок в документации Remix.
if (!(typeof document === "undefined")) {
console.log('browser environment so window is available', window.ENV)
}
Связанный
Я также приведу код TypeScript о том, как добавить свойство вwindow
interface ENV {
NODE_ENV: typeof process.env.NODE_ENV;
}
declare global {
interface Window {
ENV: ENV;
}
}
...
if (!(typeof document === "undefined")) {
console.log('browser environment so window is available', window.ENV.NODE_ENV)
}
На уровне компонента в реакции нет способа получить доступ к dom, где виден объект окна, без использования вызова ловушки useEffect и последующей ссылки на окно изнутри.
Еще одна альтернативаwindow
заключается в использовании встроенногоuseMatches
перехватите и прочитайте оттуда данные корневого загрузчика, для чего не требуется отдельный контекст React. Этот узор был позаимствован из набора Grunge.
крючки/useMatchesData.ts
export function useMatchesData(
id: string,
): Record<string, unknown> | undefined {
const matchingRoutes = useMatches();
const route = useMemo(
() => matchingRoutes.find((route) => route.id === id),
[matchingRoutes, id],
);
return route?.data;
}
крючки/useEnv.ts
function isEnv(data: any): data is ENV {
return data && typeof data === "object";
}
export function useEnvVar(name: keyof ENV) {
const data = useMatchesData("root");
if (!data || !isEnv(data.ENV)) {
throw new Error("ENV not found");
}
return data.ENV[name] || '';
}
компоненты/MyComponent.tsx
import { useEnvVar } from "~/hooks/useEnvVar";
export default function MyComponent() {
const myEnvVar = useEnvVar('MY_ENV_VAR');
return (<div>{myEnvVar}</div>);
}