You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next.js中useContext在Layout.tsx返回undefined问题排查与修复

问题原因及修复方案

问题核心原因

你在RootLayout.tsx中调用useContext(ThemeContext)时,组件本身并没有被ThemeProvider包裹。Context的作用域仅覆盖Provider内部的子组件树,RootLayout作为Provider的父级,处于Context作用域之外,所以获取到的是createContext时的默认空对象,自然darkMode返回undefined。

而Header.tsx能正常工作,是因为它被放在ThemeProvider内部的body中,属于Provider的子组件,能正确访问Context的值。


修复方案

方案1:重构RootLayout,将Context调用移到Provider内部

把需要访问Context的逻辑抽成一个嵌套组件,让它处于ThemeProvider的包裹范围内:

"use client"

import { Inter } from 'next/font/google'
import './globals.css'
import { ThemeProvider } from './contexts/ThemeContext'
import Header from './components/Header'
import { ReactNode, useContext } from 'react'

const inter = Inter({ subsets: ['latin'] })

// 嵌套组件:在ThemeProvider内部,可正常访问Context
function AppContent({ children }: { children: ReactNode }) {
  const { darkMode } = useContext(ThemeContext)
  return (
    <body className={`mx-64 ${inter.className} ${darkMode ? 'dark' : 'light'}`}>
      <Header/>
      {children}
    </body>
  )
}

export default function RootLayout({ children }: { children: ReactNode }) {
  return (
    <html lang="pt-br">
      <ThemeProvider>
        <AppContent>{children}</AppContent>
      </ThemeProvider>
    </html>
  )
}

方案2:在ThemeProvider中直接管理body类名(更简洁)

无需在RootLayout中调用Context,直接在ThemeProvider里通过useEffect同步主题状态到body的类名:

修改ThemeContext.tsx:

"use client"

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

type Theme = {
    darkMode: boolean,
    toggleDarkMode(): void
}

export const ThemeContext = createContext({} as Theme)

export function ThemeProvider({children}: {children: ReactNode}){
    const [darkMode, setDarkMode] = useState(false);

    useEffect(() => {
      // 从localStorage读取初始主题
      const storedMode = localStorage.getItem('darkMode');
      const initialMode = storedMode === 'true';
      setDarkMode(initialMode);
      // 初始化body类名
      document.body.classList.toggle('dark', initialMode);
      document.body.classList.toggle('light', !initialMode);
    }, []);

    useEffect(() => {
      // 主题切换时同步更新body类名
      document.body.classList.toggle('dark', darkMode);
      document.body.classList.toggle('light', !darkMode);
    }, [darkMode]);
  
    function toggleDarkMode() {
      const newMode = !darkMode;
      setDarkMode(newMode);
      localStorage.setItem('darkMode', String(newMode));
    };

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

修改RootLayout.tsx:

"use client"

import { Inter } from 'next/font/google'
import './globals.css'
import { ThemeProvider } from './contexts/ThemeContext'
import Header from './components/Header'
import { ReactNode } from 'react'

const inter = Inter({ subsets: ['latin'] })

export default function RootLayout({ children }: { children: ReactNode }) {
  return (
    <html lang="pt-br">
      <body className={`mx-64 ${inter.className}`}>
        <ThemeProvider>
          <Header/>
          {children}
        </ThemeProvider>
      </body>
    </html>
  )
}

额外优化建议

  1. 可以考虑将主题类名加到<html>标签上(Next.js官方推荐),避免body样式冲突,只需要把document.body换成document.documentElement即可。
  2. 初始渲染时可以避免闪烁:在RootLayout的<html>标签上先根据服务器端或localStorage的预取值设置初始类名,不过因为你已经用了客户端组件,方案2的初始化逻辑已经能覆盖大部分场景。

内容的提问来源于stack exchange,提问作者Joao

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 20:54:52