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

Posted

技术标签:

【中文标题】NextJS:生产中未定义的上下文值(在开发中工作正常)【英文标题】:NextJS: Context values undefined in production (works fine in development) 【发布时间】:2021-12-31 14:41:12 【问题描述】:

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

在开发过程中一切正常,但是在构建版本上出现了与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.loged 在点击时),但是它在生产过程中没有做任何事情(console.logs 和@987654337 @状态):

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;

我在发布之前查看的解决方案/建议无济于事。

React Context API undefined in production — reactreact-dom 在同一版本上。

提前致谢。


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

Context 最适合低频和简单的状态更新,例如 themesauthentication

Redux 除了提供更好的调试工具——Redux DevTools,更适合高频和复杂的状态更新。

P.S.2 是的,在性能方面,安装 FontAwesome 的依赖项比使用 CDN 更好。

【问题讨论】:

【参考方案1】:

感谢分享代码。它写得很好。通过阅读它,我没有看到任何问题。根据您的组件拓扑,只要您的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>
  )


如果这在生产中有效,那么它会确认它。说实话,这个问题在开发中也存在,不过可能是你的路由变化太快,一般都会隐藏这些问题。

【讨论】:

感谢windmaomao的详细回复!您的解决方案解决了我的问题,这确实是因为没有将其他页面与提供者一起包装。这也是一个问题,因为布局、开发和生产产生了不同的结果。 酷,我很高兴它有帮助 :) 我也在摸不着头脑,为什么你可以从一个写得很好的提供商那里得到一个 undefined

以上是关于NextJS:生产中未定义的上下文值(在开发中工作正常)的主要内容,如果未能解决你的问题,请参考以下文章

nextjs POST API 不能在实时环境中工作,但在本地环境中工作完美

ApnsPHP:在开发中工作但不在生产中的推送通知

如何使用来自@mui/material 的样式使情感组件选择器在 nextjs 应用程序中工作?

NuxtJS:在开发中工作但在生产中工作的路由(netlify)

Nextjs:getStaticProps 中未定义的上下文

Angular 6 - 为啥生产构建中缺少承载令牌? (在开发版本中工作正常)