React Context异常:useContext获取默认值而非传入值的排查修复
问题:ThemeContext子组件获取到默认值而非Provider传入值
我创建了一个允许子组件调用setTheme的ThemeContext,但在子组件中通过useContext导入使用时,返回的是默认值而非Provider传入的值。请问我哪里出错了?该如何修复?这是什么原因导致的?
相关代码
_document.tsx
type ThemeContextType = [string, React.Dispatch<React.SetStateAction<string>>] export const ThemeContext: React.Context<ThemeContextType> = createContext([ "light", (value: string | ((prevState: string) => string)): void => { console.log("default") }, ]) /** create a context for theme and set theme */ export default function Document(): JSX.Element { const [theme, setTheme] = useState("light") return ( <Html lang="vi"> <Head> <meta charSet="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="theme-color" content="#000000" /> <link rel="manifest" href="/manifest.json" /> <link rel="shortcut icon" href="/favicon.ico" /> </Head> <body data-theme={theme}> <ThemeContext.Provider value={[theme, setTheme]}> <Main /> </ThemeContext.Provider> <NextScript /> </body> </Html> ) }
students-info.tsx
export default function StudentsInfo({ capturing, setCapturing, }: { capturing: boolean setCapturing: React.Dispatch<React.SetStateAction<boolean>> }): JSX.Element { const [theme, setTheme] = useContext(ThemeContext) const data: Student[] = useMemo((): Student[] => students, []) const table: Table<Student> = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel(), }) useRenderEffect((): void => { if (!capturing) return const dom: HTMLElement | null = document.getElementById("students") if (!dom) return /** current theme */ const theme_: string = theme setTheme("light") /** fit dom into jspdf and save */ const pdf: jsPDF = new jsPDF("p", "pt", "a4") pdf.html(dom, { callback: (pdf: jsPDF): void => { pdf.save("students.pdf") setTheme(theme_) setCapturing(false) }, }) }, [capturing]) return ( <table id="students"> <TableHead table={table} /> <TableBody table={table} /> </table> ) }
问题原因
你错误地将ThemeContext.Provider放在了Next.js的_document.tsx中。_document.tsx是Next.js用于自定义服务端HTML渲染结构的特殊组件,它不属于客户端React应用的组件树层级。客户端组件(比如StudentsInfo)在进行hydration和日常渲染时,无法访问到这个Provider传递的上下文值,因此会回退使用createContext时传入的默认值。
修复方案
1. 单独封装ThemeContext
创建一个独立的上下文文件(比如contexts/ThemeContext.tsx),将上下文定义和Provider逻辑分离:
// contexts/ThemeContext.tsx import { createContext, useState, useContext, ReactNode } from 'react'; type ThemeContextType = [string, React.Dispatch<React.SetStateAction<string>>]; // 定义上下文 const ThemeContext = createContext<ThemeContextType>([ "light", () => console.log("default"), ]); // 封装Provider组件 export function ThemeProvider({ children }: { children: ReactNode }) { const [theme, setTheme] = useState("light"); return ( <ThemeContext.Provider value={[theme, setTheme]}> {children} </ThemeContext.Provider> ); } // 自定义钩子,简化上下文调用 export const useTheme = () => useContext(ThemeContext);
2. 清理_document.tsx
移除_document.tsx中所有ThemeContext相关代码,只保留HTML结构:
// _document.tsx import { Html, Head, Main, NextScript } from 'next/document'; export default function Document() { return ( <Html lang="vi"> <Head> <meta charSet="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="theme-color" content="#000000" /> <link rel="manifest" href="/manifest.json" /> <link rel="shortcut icon" href="/favicon.ico" /> </Head> <body> <Main /> <NextScript /> </body> </Html> ); }
3. 在_app.tsx中挂载Provider
Next.js的_app.tsx是客户端应用的根组件,所有页面都会被它包裹,在这里挂载Provider才能确保所有客户端组件都能访问上下文:
// _app.tsx import { ThemeProvider } from '@/contexts/ThemeContext'; import type { AppProps } from 'next/app'; export default function App({ Component, pageProps }: AppProps) { return ( <ThemeProvider> <Component {...pageProps} /> </ThemeProvider> ); }
4. 修改子组件调用方式
在students-info.tsx中使用自定义的useTheme钩子获取上下文:
// students-info.tsx import { useTheme } from '@/contexts/ThemeContext'; export default function StudentsInfo({ capturing, setCapturing, }: { capturing: boolean; setCapturing: React.Dispatch<React.SetStateAction<boolean>>; }) { const [theme, setTheme] = useTheme(); // 剩余代码保持不变 }
内容的提问来源于stack exchange,提问作者Fukumi
相关产品推荐
相关产品推荐

