NextJS:生产中未定义的上下文值(在开发中工作正常)

NextJS: Context values undefined in production (works fine in development)

我的 Next.js 应用程序使用 React's Context api 实现了“深色模式”功能。

开发期间一切正常,但是,构建版本出现了 Context provider 相关问题 — 全局状态显示为 undefined,无法处理。


_app.tsxThemeProvider 包装成这样:

// React & Next hooks
import React, { useEffect } from "react";
import type { AppProps } from "next/app";
import { useRouter } from "next/router";

// Irrelevant imports

// Global state management
import { Provider } from "react-redux";
import store from "../redux/store";
import { AuthProvider } from "../context/UserContext";
import { ThemeProvider } from "../context/ThemeContext";

// Components
import Layout from "../components/Layout/Layout";
import Footer from "../components/Footer/Footer";

// Irrelevant code

function MyApp({ Component, pageProps }: AppProps) {
  const router = useRouter();

  // Applying different layouts depending on page
  switch (Component.name) {
    case "HomePage":
      return (
        <Provider store={store}>
          <ThemeProvider>
            <AuthProvider>
              <Component {...pageProps} />
              <Footer color="fff" />
            </AuthProvider>
          </ThemeProvider>
        </Provider>
      );
    case "PageNotFound":
      return (
        <>
          <Component {...pageProps} />
          <Footer color="#f2f2f5" />
        </>
      );

    default:
      // Irrelevant code
  }
}
export default MyApp;

ThemeContext 正确导出其 ProviderContext:

import { createContext, ReactNode, useState, useEffect } from "react";

type themeContextType = {
  darkMode: boolean | null;
  toggleDarkMode: () => void;
};

type Props = {
  children: ReactNode;
};

// Checks for user's preference.
const getPrefColorScheme = () => {
  return !window.matchMedia
    ? null
    : window.matchMedia("(prefers-color-scheme: dark)").matches;
};

// Gets previously stored theme if it exists.
const getInitialMode = () => {
  const isReturningUser = "dark-mode" in localStorage; // Returns true if user already used the website.
  const savedMode = localStorage.getItem("dark-mode") === "true" ? true : false;
  const userPrefersDark = getPrefColorScheme(); // Gets user's colour preference.

  // If mode was saved ► return saved mode else get users general preference.
  return isReturningUser ? savedMode : userPrefersDark ? true : false;
};

export const ThemeContext = createContext<themeContextType>(
  {} as themeContextType
);

export const ThemeProvider = ({ children }: Props) => {
  // localStorage only exists on the browser (window), not on the server
  const [darkMode, setDarkMode] = useState<boolean | null>(null);

  // Getting theme from local storage upon first render
  useEffect(() => {
    setDarkMode(getInitialMode);
  }, []);

  // Prefered theme stored in local storage
  useEffect(() => {
    localStorage.setItem("dark-mode", JSON.stringify(darkMode));
  }, [darkMode]);

  const toggleDarkMode = () => {
    setDarkMode(!darkMode);
  };

  return (
    <ThemeContext.Provider value={{ darkMode, toggleDarkMode }}>
      {children}
    </ThemeContext.Provider>
  );
};

负责更新 darkMode 状态的 ThemeToggler 在开发期间正常运行(主题切换和正确的值 console.log 单击时编辑),但它在生产期间不执行任何操作(console.log 处于 undefined 状态):

import React, { FC, useContext } from "react";
import { ThemeContext } from "../../context/ThemeContext";

const ThemeToggler: FC = () => {
  const { darkMode, toggleDarkMode } = useContext(ThemeContext);

  const toggleTheme = () => {
    console.log(darkMode) // <--- darkMode is undefined during production
    toggleDarkMode();
  };
  return (
    <div className="theme-toggler">
      <i
        className={`fas ${darkMode ? "fa-sun" : "fa-moon"}`}
        data-testid="dark-mode"
        onClick={toggleTheme}
      ></i>
    </div>
  );
};

export default ThemeToggler;

我在发帖前查过的 solutions/suggestions 无济于事。

React Context API undefined in productionreactreact-dom 是同一版本。

提前致谢。


P.S。对于那些想知道为什么我同时使用 ReduxContext 进行全局状态管理的人:

P.S.2 是的,就性能而言,安装 FontAwesome 的依赖项比使用 CDN 更好。

感谢您分享代码。写得很好。通过阅读它,我没有看到任何问题。根据您的组件拓扑,只要您的 ThemeToggler 定义在任何页面组件下,您的 darkMode 就不可能是 undefined.

这是您的站点拓扑结构

  <MyApp>
    <Provider>
      // A. will not work
      <ThemeProvider>
        <HomePage>
          // B. should work
        </HomePage>
      </ThemeProvider>
      // C. will not work
    </Provider>
  </MyApp>
       
        

虽然您的 ThemeProvider 是一个自定义提供程序,但在 ThemeContext.Provider 中定义了值 {{ darkMode, toggleDarkMode }}。所以理论上你不能得到 undefined 除非你的组件 ThemeToggler 不在 HomePage 组件下。我标记了两个非工作位​​置,任何放在位置 A 或 C 下的组件都会给你 undefined.

因为你有条件 HomePage,如果你在其他页面,你可以 运行 解决这个问题。所以一般来说你应该把 ThemeProvider 包在你的路由器上面。

  <ThemeProvider>
    <AuthProvider>
      {Component.name != "PageNotFound" && (
         <Component {...pageProps} />
      )}
    </AuthProvider>
  </ThemeProvider>

你明白了,你想在启动路由器之前先通过一个主题始终存在的层。

您可以通过以下测试确认是否属于这种情况。

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <ThemeProvider>
      <AuthProvider>
         <Component {...pageProps} />
      </AuthProvider>
    </ThemeProvider>
  )
}

如果这在生产中有效,那么它证实了这一点。老实说,这个问题在开发中也存在,不过可能是你的路由变化太快,一般会把这些问题隐藏起来。