next-themes
是在next.js
应用中解决主题色切换的方案,目前在 github 已经有4.4k star
了。
它的用法也很简单,毕竟他的口号就是两行代码完美解决暗色模式,那么现在我们就来看看吧
本文是在 @next14 的环境,即 app router
如果是 page router,请去官网自助食用
用法
首先安装依赖
$ npm install next-themes
# or
$ yarn add next-themes
然后在layout
中使用,这里suppressHydrationWarning
其实就是帮我们处理添加data-theme
,以方便我们切换主题
// app/layout.jsx
import { ThemeProvider } from 'next-themes'
export default function Layout({ children }) {
return (
<html suppressHydrationWarning>
<head />
<body>
<ThemeProvider>{children}</ThemeProvider>
</body>
</html>
)
}
// 如果你是 tailwind,那么需要添加 attribute="class" 属性
// 具体参考 https://github.com/pacocoursey/next-themes?tab=readme-ov-file#with-tailwind
接着,我们通过api setTheme
切换主题;不难发现,它是通过useTheme()
解构出来的,使用很方便
import { useTheme } from 'next-themes'
const ThemeChanger = () => {
const { theme, setTheme } = useTheme()
return (
<div>
当前主题是: {theme}
<button onClick={() => setTheme('light')}>亮色模式</button>
<button onClick={() => setTheme('dark')}>暗色模式</button>
</div>
)
}
需要注意的是,当我们setTheme
时,将会改变全局的主题色,也就是说其他页面的主题也会被我们影响到,因为上面我们说过,它的原理其实还是在html
标签上修改data-theme
。那么如果我们希望某个独特的页面固定为暗色主题,该怎么办呢?
下面就要开始介绍ThemeProvider
的属性了,这里仅挑选重要的几个属性来讲~
storageKey = 'theme'
: 在 storage 里存储的 key,也就是说当我们 setTheme 时,也会在 localStoreage 里存储forcedTheme
: 上面的问题正需要这个属性了,这个属性是当前页面的强制主题色,使用方式如下function Providers({ children }) { const isForceDark = '根据路径或者其他方式判断' return( <ThemeProvider attribute="class" themes={['light', 'dark']} forcedTheme={isForceDark ? 'dark' : null} > {children} </ThemeProvider> ) }
enableSystem = true
: 是否跟随系统设置切换亮暗色
Hydration Error
在过去的一个月里,我的Next
应用遇到这样一个问题:本地开发没有报错,但部署上线后却报了 10 个错误,打开一看全是hydration
相关的
我们知道,hydration
报错本质上就是ssr
和csr
不匹配,但线上报错本地不报错,着实让我摸不着头脑。这时组长给我明确了一个方向就是:ssr 的数据异常
还别说,打开network
查看服务端渲染返回的HTML,还真找到了我的代码纰漏。不过修复后却依然存在这个报错,这是为什么呢?
原来就是这个next-themes
库导致的问题,因为当 ssr 的时候其实拿不到theme
此时为空,所以需要在 ssr 的时候返回 null,等到 csr 的时候再返回真正的UI
import { useState, useEffect } from 'react'
import { useTheme } from 'next-themes'
const ThemeSwitch = () => {
const [mounted, setMounted] = useState(false)
const { theme, setTheme } = useTheme()
// useEffect only runs on the client, so now we can safely show the UI
useEffect(() => {
setMounted(true)
}, [])
if (!mounted) {
return null
}
return (
<select value={theme} onChange={e => setTheme(e.target.value)}>
<option value="system">System</option>
<option value="dark">Dark</option>
<option value="light">Light</option>
</select>
)
}
export default ThemeSwitch
原文链接:https://juejin.cn/post/7356080597600059430 作者:祇祇