如何让Next.js页面感知暗黑模式切换并适配Cytoscape样式?
解决Next.js + Tailwind + Cytoscape暗黑模式适配问题
问题背景
我正在开发第一个Next.js+Tailwind简易应用,包含固定头部和侧边栏,侧边栏内置暗黑/亮色模式切换按钮,用next-themes结合Tailwind实现模式切换的流程顺畅。但其中一个主内容页的有向图用Cytoscape实现,它不支持Tailwind类样式,样式表需手动定义,希望节点和边的颜色能随暗黑模式切换同步变化。
Cytoscape样式表示例:
const stylesheet = [ { selector: 'node', style: { 'backgroundColor': "#33FF66", // 需要随暗黑模式变色 }, { selector: "edge", style: { 'curve-style': 'bezier', 'line-color': '#333333', // 需要随暗黑模式变色 }, }, ];
最初的RootLayout代码:
export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { const [sidebarOpen, setSidebarOpen] = useState(false); return ( <html lang="en"> <body className={`${inter.className} bg-slate-50 dark:bg-slate-800`}> <div className="grid min-h-screen grid-rows-header"> <div> <Header onMenuButtonClick={() => setSidebarOpen((prev) => !prev)} /> </div> <div className="flex min-h-screen"> <div className=""> <Sidebar open={sidebarOpen} setOpen={setSidebarOpen} /> </div> <div className="mt-[50px] flex-1"> <main>{children}</main> </div> </div> </div> </body> </html> ); }
最初的困惑:计划创建stylesheetLight和stylesheetDark两套样式表实现切换,但不知道如何在图表页面监听侧边栏切换按钮的事件。
更新1:将ThemeProvider移至RootLayout
按照建议把ThemeProvider从侧边栏组件移到RootLayout中,让所有子组件都能通过useTheme钩子获取主题状态:
export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { const [sidebarOpen, setSidebarOpen] = useState(false); return ( <html lang="en"> <body className={`${inter.className} bg-slate-50 dark:bg-slate-800`}> <ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange> <div className="grid min-h-screen grid-rows-header"> <div> <Header onMenuButtonClick={() => setSidebarOpen((prev) => !prev)} /> </div> <div className="flex min-h-screen"> <div className=""> <Sidebar open={sidebarOpen} setOpen={setSidebarOpen} /> </div> <div className="mt-[50px] flex-1"> <main>{children}</main> </div> </div> </div> </ThemeProvider> </body> </html> ); }
此时图表页面可以通过useTheme获取主题变化,但直接在组件顶层根据theme设置stylesheet会触发Too Many Re-renders错误:
export default function GraphPage() { const { theme, setTheme } = useTheme() const [topicGraph, setTopicGraph] = useState<any[] | null>(null); const [stylesheet, setStylesheet] = useState<any | null>(null) console.log("Theme:", theme) // 切换模式时正常打印 // 错误写法:会导致无限重渲染 // if (theme === "light") { // setStylesheet(stylesheetLight); // } else { // setStylesheet(stylesheetDark); // } useEffect(() => { getGraph().then((jsonResponse) => { // 这里设置样式表正常工作 if (theme === "light") { setStylesheet(stylesheetLight); } else { setStylesheet(stylesheetDark); } // 调用API获取图表数据... setTopicGraph(graph); }); }, [theme]); return ( <div> <CytoscapeComponent elements={topicGraph} style={{ width: 'w-full', height: 'calc(100vh - 50px)' }} stylesheet={stylesheet} layout={defaultLayout} wheelSensitivity={0.15} cy={(cy) => { setListeners(cy); }} /> </div> ) }
更新2:解决无限重渲染问题
将setStylesheet逻辑移入依赖theme的useEffect块中,确保只有当theme变化时才更新样式表,彻底避免无限重渲染:
核心修改后的代码片段:
useEffect(() => { // 根据当前主题切换样式表 if (theme === "light") { setStylesheet(stylesheetLight); } else { setStylesheet(stylesheetDark); } // 若主题变化时无需重新获取数据,可移除以下API调用 getGraph().then((jsonResponse) => { setTopicGraph(graph); }); }, [theme]); // 依赖项仅包含theme,确保仅在theme变化时执行
这样既实现了Cytoscape图表样式随暗黑模式同步切换,又解决了重渲染问题。
内容的提问来源于stack exchange,提问作者Christian
相关产品推荐
相关产品推荐

